commit
ef92ee51a5
@ -6,8 +6,8 @@ android {
|
||||
applicationId "net.schueller.peertube"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 28
|
||||
versionCode 105
|
||||
versionName "1.0.5"
|
||||
versionCode 106
|
||||
versionName "1.0.6"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
@ -44,7 +44,11 @@ android {
|
||||
// implementation 'org.webrtc:google-webrtc:1.0.+'
|
||||
|
||||
// video player
|
||||
implementation 'com.google.android.exoplayer:exoplayer:2.8.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.8.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-dash:2.8.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.8.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-hls:2.8.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.8.1'
|
||||
// implementation 'com.devbrackets.android:exomedia:4.1.0'
|
||||
|
||||
// testing
|
||||
|
@ -50,5 +50,6 @@
|
||||
android:exported="false" />
|
||||
|
||||
<activity android:name=".activity.SelectServerActivity"/>
|
||||
<service android:name=".service.VideoPlayerService" />
|
||||
</application>
|
||||
</manifest>
|
@ -1,20 +1,27 @@
|
||||
package net.schueller.peertube.activity;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.os.Environment;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.PopupMenu;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
@ -25,32 +32,21 @@ import com.github.se_bastiaan.torrentstream.Torrent;
|
||||
import com.github.se_bastiaan.torrentstream.TorrentOptions;
|
||||
import com.github.se_bastiaan.torrentstream.TorrentStream;
|
||||
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener;
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
import net.schueller.peertube.R;
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
import net.schueller.peertube.helper.MetaDataHelper;
|
||||
import net.schueller.peertube.intents.Intents;
|
||||
import net.schueller.peertube.model.Avatar;
|
||||
import net.schueller.peertube.model.Video;
|
||||
|
||||
import net.schueller.peertube.network.GetVideoDataService;
|
||||
import net.schueller.peertube.network.RetrofitInstance;
|
||||
|
||||
import net.schueller.peertube.service.VideoPlayerService;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
@ -61,95 +57,61 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
||||
|
||||
private ProgressBar progressBar;
|
||||
private PlayerView simpleExoPlayerView;
|
||||
private SimpleExoPlayer player;
|
||||
private Intent videoPlayerIntent;
|
||||
private Context context = this;
|
||||
|
||||
boolean mBound = false;
|
||||
VideoPlayerService mService;
|
||||
|
||||
private ServiceConnection mConnection = new ServiceConnection() {
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
Log.d(TAG, "onServiceConnected");
|
||||
VideoPlayerService.LocalBinder binder = (VideoPlayerService.LocalBinder) service;
|
||||
mService = binder.getService();
|
||||
|
||||
// 2. Create the player
|
||||
simpleExoPlayerView.setPlayer(mService.player);
|
||||
mBound = true;
|
||||
|
||||
loadVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
Log.d(TAG, "onServiceDisconnected");
|
||||
simpleExoPlayerView.setPlayer(null);
|
||||
mBound = false;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_video_play);
|
||||
|
||||
// get video ID
|
||||
Intent intent = getIntent();
|
||||
String videoID = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID);
|
||||
Log.v(TAG, "click: " + videoID);
|
||||
|
||||
progressBar = findViewById(R.id.progress);
|
||||
progressBar.setMax(100);
|
||||
|
||||
// PlayerView videoView = findViewById(R.id.video_view);
|
||||
simpleExoPlayerView = new PlayerView(this);
|
||||
simpleExoPlayerView = findViewById(R.id.video_view);
|
||||
|
||||
// 1. Create a default TrackSelector
|
||||
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||
TrackSelection.Factory videoTrackSelectionFactory =
|
||||
new AdaptiveTrackSelection.Factory(bandwidthMeter);
|
||||
TrackSelector trackSelector =
|
||||
new DefaultTrackSelector(videoTrackSelectionFactory);
|
||||
|
||||
// 2. Create the player
|
||||
player = ExoPlayerFactory.newSimpleInstance(getApplicationContext(), trackSelector);
|
||||
simpleExoPlayerView.setPlayer(player);
|
||||
|
||||
// get video details from api
|
||||
String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
|
||||
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
|
||||
|
||||
Call<Video> call = service.getVideoData(videoID);
|
||||
|
||||
call.enqueue(new Callback<Video>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Video> call, @NonNull Response<Video> response) {
|
||||
|
||||
// Toast.makeText(TorrentVideoPlayActivity.this, response.body().getDescription(), Toast.LENGTH_SHORT).show();
|
||||
|
||||
TextView videoName = findViewById(R.id.name);
|
||||
TextView videoDescription = findViewById(R.id.description);
|
||||
TextView videoMeta = findViewById(R.id.videoMeta);
|
||||
|
||||
try {
|
||||
|
||||
videoName.setText(response.body().getName());
|
||||
videoDescription.setText(response.body().getDescription());
|
||||
|
||||
videoMeta.setText(
|
||||
MetaDataHelper.getMetaString(
|
||||
response.body().getCreatedAt(),
|
||||
response.body().getViews(),
|
||||
getBaseContext()
|
||||
)
|
||||
);
|
||||
|
||||
String streamUrl = response.body().getFiles().get(0).getFileUrl();
|
||||
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
if (sharedPref.getBoolean("pref_torrent_player",false)) {
|
||||
streamUrl = response.body().getFiles().get(0).getTorrentUrl();
|
||||
TorrentStream torrentStream = setupTorrentStream();
|
||||
torrentStream.startStream(streamUrl);
|
||||
} else {
|
||||
setupVideoView(Uri.parse(streamUrl));
|
||||
}
|
||||
|
||||
Log.v(TAG, streamUrl);
|
||||
|
||||
|
||||
} catch (NullPointerException e) {
|
||||
e.getStackTrace();
|
||||
}
|
||||
videoPlayerIntent = new Intent(this, VideoPlayerService.class);
|
||||
bindService(videoPlayerIntent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Video> call, @NonNull Throwable t) {
|
||||
Log.wtf(TAG, t.fillInStackTrace());
|
||||
Toast.makeText(VideoPlayActivity.this, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
private void startPlayer()
|
||||
{
|
||||
Util.startForegroundService(context, videoPlayerIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Torrent Playback
|
||||
*
|
||||
* @return torrent stream
|
||||
*/
|
||||
private TorrentStream setupTorrentStream() {
|
||||
|
||||
TorrentOptions torrentOptions = new TorrentOptions.Builder()
|
||||
@ -163,9 +125,8 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
||||
@Override
|
||||
public void onStreamReady(Torrent torrent) {
|
||||
Log.d(TAG, "Ready");
|
||||
|
||||
setupVideoView(Uri.fromFile(torrent.getVideoFile()));
|
||||
|
||||
mService.setCurrentStreamUrl(Uri.fromFile(torrent.getVideoFile()).toString());
|
||||
startPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -201,28 +162,12 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
||||
return torrentStream;
|
||||
}
|
||||
|
||||
private void setupVideoView(Uri videoStream) {
|
||||
|
||||
Log.d(TAG, "Play Video");
|
||||
|
||||
// Produces DataSource instances through which media data is loaded.
|
||||
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(),
|
||||
Util.getUserAgent(getApplicationContext(), "PeerTube"), null);
|
||||
|
||||
// This is the MediaSource representing the media to be played.
|
||||
MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(videoStream);
|
||||
|
||||
// Prepare the player with the source.
|
||||
player.prepare(videoSource);
|
||||
|
||||
// Auto play
|
||||
player.setPlayWhenReady(true);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
|
||||
Log.v(TAG, "onConfigurationChanged()...");
|
||||
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
TextView nameView = findViewById(R.id.name);
|
||||
@ -256,8 +201,105 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void loadVideo()
|
||||
{
|
||||
// get video ID
|
||||
Intent intent = getIntent();
|
||||
String videoID = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID);
|
||||
Log.v(TAG, "click: " + videoID);
|
||||
|
||||
// get video details from api
|
||||
String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
|
||||
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
|
||||
|
||||
Call<Video> call = service.getVideoData(videoID);
|
||||
|
||||
call.enqueue(new Callback<Video>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Video> call, @NonNull Response<Video> response) {
|
||||
|
||||
// Toast.makeText(TorrentVideoPlayActivity.this, response.body().getDescription(), Toast.LENGTH_SHORT).show();
|
||||
|
||||
// TODO: remove this code duplication, similar code as in video list rows
|
||||
|
||||
TextView videoName = findViewById(R.id.name);
|
||||
TextView videoDescription = findViewById(R.id.description);
|
||||
TextView videoOwner = findViewById(R.id.videoOwner);
|
||||
TextView videoMeta = findViewById(R.id.videoMeta);
|
||||
ImageView avatarView = findViewById(R.id.avatar);
|
||||
ImageButton moreButton = findViewById(R.id.moreButton);
|
||||
|
||||
Video video = response.body();
|
||||
|
||||
mService.setCurrentVideo(video);
|
||||
|
||||
String baseUrl = APIUrlHelper.getUrl(context);
|
||||
|
||||
Avatar avatar = video.getAccount().getAvatar();
|
||||
if (avatar != null) {
|
||||
String avatarPath = avatar.getPath();
|
||||
Picasso.with(context)
|
||||
.load(baseUrl + avatarPath)
|
||||
.into(avatarView);
|
||||
}
|
||||
|
||||
videoName.setText(video.getName());
|
||||
videoMeta.setText(
|
||||
MetaDataHelper.getMetaString(
|
||||
video.getCreatedAt(),
|
||||
video.getViews(),
|
||||
getBaseContext()
|
||||
)
|
||||
);
|
||||
videoOwner.setText(
|
||||
MetaDataHelper.getOwnerString(video.getAccount().getName(),
|
||||
video.getAccount().getHost(),
|
||||
context
|
||||
)
|
||||
);
|
||||
videoDescription.setText(video.getDescription());
|
||||
|
||||
moreButton.setOnClickListener(v -> {
|
||||
PopupMenu popup = new PopupMenu(context, v);
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_share:
|
||||
Intents.Share(context, video);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
popup.inflate(R.menu.menu_video_row_mode);
|
||||
popup.show();
|
||||
});
|
||||
|
||||
mService.setCurrentStreamUrl(video.getFiles().get(0).getFileUrl());
|
||||
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
if (sharedPref.getBoolean("pref_torrent_player", false)) {
|
||||
|
||||
String stream = video.getFiles().get(0).getTorrentUrl();
|
||||
TorrentStream torrentStream = setupTorrentStream();
|
||||
torrentStream.startStream(stream);
|
||||
} else {
|
||||
startPlayer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Video> call, @NonNull Throwable t) {
|
||||
Log.wtf(TAG, t.fillInStackTrace());
|
||||
Toast.makeText(VideoPlayActivity.this, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoEnabled(DecoderCounters counters) {
|
||||
Log.v(TAG, "onVideoEnabled()...");
|
||||
|
||||
}
|
||||
|
||||
@ -288,15 +330,14 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
||||
|
||||
@Override
|
||||
public void onVideoDisabled(DecoderCounters counters) {
|
||||
|
||||
Log.v(TAG, "onVideoDisabled()...");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.v(TAG, "onDestroy()...");
|
||||
player.release();
|
||||
simpleExoPlayerView.setPlayer(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -314,6 +355,8 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
unbindService(mConnection);
|
||||
mBound = false;
|
||||
Log.v(TAG, "onStop()...");
|
||||
}
|
||||
|
||||
@ -322,4 +365,5 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
||||
super.onStart();
|
||||
Log.v(TAG, "onStart()...");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package net.schueller.peertube.adapter;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.PopupMenu;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
@ -16,6 +18,7 @@ import net.schueller.peertube.R;
|
||||
import net.schueller.peertube.activity.VideoPlayActivity;
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
import net.schueller.peertube.helper.MetaDataHelper;
|
||||
import net.schueller.peertube.intents.Intents;
|
||||
import net.schueller.peertube.model.Avatar;
|
||||
import net.schueller.peertube.model.Video;
|
||||
|
||||
@ -28,7 +31,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
||||
|
||||
private ArrayList<Video> videoList;
|
||||
private Context context;
|
||||
private String apiBaseURL;
|
||||
private String baseUrl;
|
||||
|
||||
public VideoAdapter(ArrayList<Video> videoList, Context context) {
|
||||
this.videoList = videoList;
|
||||
@ -41,7 +44,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||
View view = layoutInflater.inflate(R.layout.row_video, parent, false);
|
||||
|
||||
apiBaseURL = APIUrlHelper.getUrl(context);
|
||||
baseUrl = APIUrlHelper.getUrl(context);
|
||||
|
||||
return new VideoViewHolder(view);
|
||||
}
|
||||
@ -50,7 +53,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
||||
public void onBindViewHolder(@NonNull VideoViewHolder holder, int position) {
|
||||
|
||||
Picasso.with(this.context)
|
||||
.load(apiBaseURL + videoList.get(position).getPreviewPath())
|
||||
.load(baseUrl + videoList.get(position).getPreviewPath())
|
||||
.into(holder.thumb);
|
||||
|
||||
|
||||
@ -58,7 +61,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
||||
if (avatar != null) {
|
||||
String avatarPath = avatar.getPath();
|
||||
Picasso.with(this.context)
|
||||
.load(apiBaseURL + avatarPath)
|
||||
.load(baseUrl + avatarPath)
|
||||
.into(holder.avatar);
|
||||
}
|
||||
|
||||
@ -91,6 +94,23 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
||||
|
||||
});
|
||||
|
||||
holder.moreButton.setOnClickListener(v -> {
|
||||
|
||||
PopupMenu popup = new PopupMenu(context, v);
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_share:
|
||||
Intents.Share(context, videoList.get(position));
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
popup.inflate(R.menu.menu_video_row_mode);
|
||||
popup.show();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void setData(ArrayList<Video> data) {
|
||||
@ -112,6 +132,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
||||
|
||||
TextView name, videoMeta, videoOwner;
|
||||
ImageView thumb, avatar;
|
||||
ImageButton moreButton;
|
||||
View mView;
|
||||
|
||||
VideoViewHolder(View itemView) {
|
||||
@ -121,6 +142,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
||||
avatar = itemView.findViewById(R.id.avatar);
|
||||
videoMeta = itemView.findViewById(R.id.videoMeta);
|
||||
videoOwner = itemView.findViewById(R.id.videoOwner);
|
||||
moreButton = itemView.findViewById(R.id.moreButton);
|
||||
mView = itemView;
|
||||
}
|
||||
}
|
||||
|
@ -23,4 +23,8 @@ public class APIUrlHelper{
|
||||
public static String getUrlWithVersion(Context context) {
|
||||
return APIUrlHelper.getUrl(context) + "/api/v1/";
|
||||
}
|
||||
|
||||
public static String getShareUrl(Context context, String videoUuid) {
|
||||
return APIUrlHelper.getUrl(context) + "/videos/watch/" + videoUuid;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package net.schueller.peertube.intents;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import net.schueller.peertube.helper.APIUrlHelper;
|
||||
import net.schueller.peertube.model.Video;
|
||||
|
||||
|
||||
public class Intents {
|
||||
|
||||
|
||||
/**
|
||||
* https://troll.tv/videos/watch/6edbd9d1-e3c5-4a6c-8491-646e2020469c
|
||||
*
|
||||
* @param context context
|
||||
* @param video video
|
||||
*/
|
||||
public static void Share(Context context, Video video) {
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, video.getName());
|
||||
intent.putExtra(Intent.EXTRA_TEXT, APIUrlHelper.getShareUrl(context, video.getUuid()) );
|
||||
intent.setType("text/plain");
|
||||
|
||||
context.startActivity(intent);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
package net.schueller.peertube.service;
|
||||
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import net.schueller.peertube.R;
|
||||
import net.schueller.peertube.activity.VideoPlayActivity;
|
||||
import net.schueller.peertube.helper.MetaDataHelper;
|
||||
import net.schueller.peertube.model.Video;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
|
||||
public class VideoPlayerService extends Service {
|
||||
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
|
||||
private static final String PLAYBACK_CHANNEL_ID = "playback_channel";
|
||||
private static final Integer PLAYBACK_NOTIFICATION_ID = 1;
|
||||
|
||||
public SimpleExoPlayer player;
|
||||
|
||||
private Video currentVideo;
|
||||
private String currentStreamUrl;
|
||||
|
||||
private PlayerNotificationManager playerNotificationManager;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Log.v("VideoPlayerService", "onCreate...");
|
||||
|
||||
player = ExoPlayerFactory.newSimpleInstance(getApplicationContext(), new DefaultTrackSelector());
|
||||
|
||||
}
|
||||
|
||||
public class LocalBinder extends Binder {
|
||||
|
||||
public VideoPlayerService getService() {
|
||||
// Return this instance of VideoPlayerService so clients can call public methods
|
||||
return VideoPlayerService.this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
Log.v("VideoPlayerService", "onDestroy...");
|
||||
|
||||
playerNotificationManager.setPlayer(null);
|
||||
player.release();
|
||||
player = null;
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.v("VideoPlayerService", "onStartCommand...");
|
||||
playVideo();
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
|
||||
public void setCurrentVideo(Video video)
|
||||
{
|
||||
Log.v("VideoPlayerService", "setCurrentVideo...");
|
||||
currentVideo = video;
|
||||
}
|
||||
|
||||
public void setCurrentStreamUrl(String streamUrl)
|
||||
{
|
||||
Log.v("VideoPlayerService", "setCurrentStreamUrl...");
|
||||
currentStreamUrl = streamUrl;
|
||||
}
|
||||
|
||||
public void playVideo()
|
||||
{
|
||||
Context context = this;
|
||||
|
||||
Log.v("VideoPlayerService", "playVideo...");
|
||||
|
||||
// Produces DataSource instances through which media data is loaded.
|
||||
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(),
|
||||
Util.getUserAgent(getApplicationContext(), "PeerTube"), null);
|
||||
|
||||
// This is the MediaSource representing the media to be played.
|
||||
ExtractorMediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(Uri.parse(currentStreamUrl));
|
||||
|
||||
// Prepare the player with the source.
|
||||
player.prepare(videoSource);
|
||||
|
||||
// Auto play
|
||||
player.setPlayWhenReady(true);
|
||||
|
||||
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
|
||||
context, PLAYBACK_CHANNEL_ID, R.string.playback_channel_name,
|
||||
PLAYBACK_NOTIFICATION_ID,
|
||||
new PlayerNotificationManager.MediaDescriptionAdapter() {
|
||||
|
||||
@Override
|
||||
public String getCurrentContentTitle(Player player) {
|
||||
return currentVideo.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PendingIntent createCurrentContentIntent(Player player) {
|
||||
Intent intent = new Intent(context, VideoPlayActivity.class);
|
||||
return PendingIntent.getActivity(context, 0, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getCurrentContentText(Player player) {
|
||||
return MetaDataHelper.getMetaString(
|
||||
currentVideo.getCreatedAt(),
|
||||
currentVideo.getViews(),
|
||||
getBaseContext()
|
||||
);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
playerNotificationManager.setSmallIcon(R.drawable.ic_peertube_bw);
|
||||
|
||||
playerNotificationManager.setNotificationListener(
|
||||
new PlayerNotificationManager.NotificationListener() {
|
||||
@Override
|
||||
public void onNotificationStarted(int notificationId, Notification notification) {
|
||||
startForeground(notificationId, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationCancelled(int notificationId) {
|
||||
Log.v("VideoPlayerService", "onNotificationCancelled...");
|
||||
|
||||
// TODO: only kill the notification if we no longer have a bound activity
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
playerNotificationManager.setPlayer(player);
|
||||
|
||||
}
|
||||
|
||||
}
|
16
app/src/main/res/drawable/ic_peertube_bw.xml
Normal file
16
app/src/main/res/drawable/ic_peertube_bw.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="22">
|
||||
<path
|
||||
android:pathData="m0.0336,0.34v10.667l8,-5.333"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="m0.0336,11.007v10.667l8,-5.333"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="m8.0336,5.673v10.667l8,-5.333"
|
||||
android:fillColor="#000000"/>
|
||||
|
||||
</vector>
|
@ -34,32 +34,83 @@
|
||||
android:indeterminate="false"
|
||||
android:max="100" />
|
||||
|
||||
<android.support.v7.widget.LinearLayoutCompat
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/progress"
|
||||
android:padding="16dp"
|
||||
android:padding="6dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="0dp"
|
||||
android:contentDescription="@string/video_row_account_avatar"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoMeta"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/name"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoOwner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/videoMeta"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/moreButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="45dp"
|
||||
android:layout_marginStart="-16dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_toEndOf="@+id/name"
|
||||
android:background="@null"
|
||||
android:contentDescription="@string/descr_overflow_button"
|
||||
android:src="@drawable/ic_action_more_vert" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/videoMeta"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="35dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" />
|
||||
|
||||
</android.support.v7.widget.LinearLayoutCompat>
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
|
74
app/src/main/res/layout/exo_playback_control_view.xml
Normal file
74
app/src/main/res/layout/exo_playback_control_view.xml
Normal file
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layoutDirection="ltr"
|
||||
android:background="#CC000000"
|
||||
android:orientation="vertical"
|
||||
tools:targetApi="28">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="4dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton android:id="@id/exo_rew"
|
||||
style="@style/ExoMediaButton.Rewind"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_shuffle"
|
||||
style="@style/ExoMediaButton.Shuffle"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_repeat_toggle"
|
||||
style="@style/ExoMediaButton"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_play"
|
||||
style="@style/ExoMediaButton.Play"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_pause"
|
||||
style="@style/ExoMediaButton.Pause"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_ffwd"
|
||||
style="@style/ExoMediaButton.FastForward"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView android:id="@id/exo_position"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="#FFBEBEBE"/>
|
||||
|
||||
<com.google.android.exoplayer2.ui.DefaultTimeBar
|
||||
android:id="@id/exo_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="26dp"/>
|
||||
|
||||
<TextView android:id="@id/exo_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="#FFBEBEBE"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -19,38 +19,45 @@
|
||||
android:id="@+id/thumb"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxHeight="300dp"
|
||||
android:contentDescription="@string/video_row_video_thumbnail"
|
||||
android:scaleType="fitXY"
|
||||
android:adjustViewBounds="true"
|
||||
/>
|
||||
android:contentDescription="@string/video_row_video_thumbnail"
|
||||
android:maxHeight="300dp"
|
||||
android:scaleType="fitXY" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<de.hdodenhof.circleimageview.CircleImageView xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_below="@+id/thumb"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="0dp"
|
||||
android:contentDescription="@string/video_row_account_avatar"
|
||||
android:layout_below="@id/thumb"
|
||||
android:layout_alignParentStart="true"/>
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:paddingTop="12dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/thumb"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:layout_marginEnd="24dp"
|
||||
|
||||
android:paddingTop="0dp"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoMeta"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_below="@+id/name"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
|
||||
<TextView
|
||||
@ -58,8 +65,27 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/videoMeta"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/moreButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="45dp"
|
||||
android:layout_below="@+id/thumb"
|
||||
|
||||
android:layout_marginStart="-16dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
|
||||
android:layout_toEndOf="@+id/name"
|
||||
|
||||
android:background="@null"
|
||||
android:contentDescription="@string/descr_overflow_button"
|
||||
android:src="@drawable/ic_action_more_vert" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
7
app/src/main/res/menu/menu_video_row_mode.xml
Normal file
7
app/src/main/res/menu/menu_video_row_mode.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/menu_share"
|
||||
android:icon="@drawable/ic_action_share"
|
||||
android:title="@string/menu_share" />
|
||||
</menu>
|
@ -49,12 +49,15 @@
|
||||
<string name="pref_description_show_nsfw">NSFW content will be shown if enabled.</string>
|
||||
<string name="title_activity_url_video_play">UrlVideoPlayActivity</string>
|
||||
<string name="pref_title_torrent_player">Torrent Video Player</string>
|
||||
<string name="pref_description_torrent_player">Video playback via a torrent stream. This requires Storage Permissions.</string>
|
||||
<string name="pref_description_torrent_player">Video playback via a torrent stream. This requires Storage Permissions. (Alpha, not stable!)</string>
|
||||
<string name="pref_title_license">License</string>
|
||||
<string name="pref_description_license">\n<b>GNU Affero General Public License v3.0</b>\n\nPermissions of this strongest copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. When a modified version is used to provide a service over a network, the complete source code of the modified version must be made available.</string>
|
||||
<string name="pref_title_version">Version</string>
|
||||
<string name="search_hint">Search PeerTube</string>
|
||||
<string name="title_activity_search">Search</string>
|
||||
<string name="no_data_available">No Results</string>
|
||||
<string name="descr_overflow_button">More</string>
|
||||
<string name="menu_share">Share</string>
|
||||
<string name="playback_channel_name">PeerTube</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user