commit
ef92ee51a5
@ -6,8 +6,8 @@ android {
|
|||||||
applicationId "net.schueller.peertube"
|
applicationId "net.schueller.peertube"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 105
|
versionCode 106
|
||||||
versionName "1.0.5"
|
versionName "1.0.6"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
@ -44,7 +44,11 @@ android {
|
|||||||
// implementation 'org.webrtc:google-webrtc:1.0.+'
|
// implementation 'org.webrtc:google-webrtc:1.0.+'
|
||||||
|
|
||||||
// video player
|
// 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'
|
// implementation 'com.devbrackets.android:exomedia:4.1.0'
|
||||||
|
|
||||||
// testing
|
// testing
|
||||||
|
@ -50,5 +50,6 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity android:name=".activity.SelectServerActivity"/>
|
<activity android:name=".activity.SelectServerActivity"/>
|
||||||
|
<service android:name=".service.VideoPlayerService" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
@ -1,20 +1,27 @@
|
|||||||
package net.schueller.peertube.activity;
|
package net.schueller.peertube.activity;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.PopupMenu;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
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.TorrentOptions;
|
||||||
import com.github.se_bastiaan.torrentstream.TorrentStream;
|
import com.github.se_bastiaan.torrentstream.TorrentStream;
|
||||||
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener;
|
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.Format;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
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.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.util.Util;
|
||||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
import net.schueller.peertube.R;
|
import net.schueller.peertube.R;
|
||||||
import net.schueller.peertube.helper.APIUrlHelper;
|
import net.schueller.peertube.helper.APIUrlHelper;
|
||||||
import net.schueller.peertube.helper.MetaDataHelper;
|
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.model.Video;
|
||||||
|
|
||||||
import net.schueller.peertube.network.GetVideoDataService;
|
import net.schueller.peertube.network.GetVideoDataService;
|
||||||
import net.schueller.peertube.network.RetrofitInstance;
|
import net.schueller.peertube.network.RetrofitInstance;
|
||||||
|
import net.schueller.peertube.service.VideoPlayerService;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
@ -61,95 +57,61 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
|||||||
|
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
private PlayerView simpleExoPlayerView;
|
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
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_video_play);
|
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 = findViewById(R.id.progress);
|
||||||
progressBar.setMax(100);
|
progressBar.setMax(100);
|
||||||
|
|
||||||
// PlayerView videoView = findViewById(R.id.video_view);
|
|
||||||
simpleExoPlayerView = new PlayerView(this);
|
simpleExoPlayerView = new PlayerView(this);
|
||||||
simpleExoPlayerView = findViewById(R.id.video_view);
|
simpleExoPlayerView = findViewById(R.id.video_view);
|
||||||
|
|
||||||
// 1. Create a default TrackSelector
|
videoPlayerIntent = new Intent(this, VideoPlayerService.class);
|
||||||
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
bindService(videoPlayerIntent, mConnection, Context.BIND_AUTO_CREATE);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void startPlayer()
|
||||||
public void onFailure(@NonNull Call<Video> call, @NonNull Throwable t) {
|
{
|
||||||
Log.wtf(TAG, t.fillInStackTrace());
|
Util.startForegroundService(context, videoPlayerIntent);
|
||||||
Toast.makeText(VideoPlayActivity.this, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Torrent Playback
|
||||||
|
*
|
||||||
|
* @return torrent stream
|
||||||
|
*/
|
||||||
private TorrentStream setupTorrentStream() {
|
private TorrentStream setupTorrentStream() {
|
||||||
|
|
||||||
TorrentOptions torrentOptions = new TorrentOptions.Builder()
|
TorrentOptions torrentOptions = new TorrentOptions.Builder()
|
||||||
@ -163,9 +125,8 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
|||||||
@Override
|
@Override
|
||||||
public void onStreamReady(Torrent torrent) {
|
public void onStreamReady(Torrent torrent) {
|
||||||
Log.d(TAG, "Ready");
|
Log.d(TAG, "Ready");
|
||||||
|
mService.setCurrentStreamUrl(Uri.fromFile(torrent.getVideoFile()).toString());
|
||||||
setupVideoView(Uri.fromFile(torrent.getVideoFile()));
|
startPlayer();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -201,28 +162,12 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
|||||||
return torrentStream;
|
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
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
|
||||||
|
Log.v(TAG, "onConfigurationChanged()...");
|
||||||
|
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
|
|
||||||
TextView nameView = findViewById(R.id.name);
|
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
|
@Override
|
||||||
public void onVideoEnabled(DecoderCounters counters) {
|
public void onVideoEnabled(DecoderCounters counters) {
|
||||||
|
Log.v(TAG, "onVideoEnabled()...");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,15 +330,14 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoDisabled(DecoderCounters counters) {
|
public void onVideoDisabled(DecoderCounters counters) {
|
||||||
|
Log.v(TAG, "onVideoDisabled()...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
Log.v(TAG, "onDestroy()...");
|
simpleExoPlayerView.setPlayer(null);
|
||||||
player.release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -314,6 +355,8 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
|||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
unbindService(mConnection);
|
||||||
|
mBound = false;
|
||||||
Log.v(TAG, "onStop()...");
|
Log.v(TAG, "onStop()...");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,4 +365,5 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
|
|||||||
super.onStart();
|
super.onStart();
|
||||||
Log.v(TAG, "onStart()...");
|
Log.v(TAG, "onStart()...");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ package net.schueller.peertube.adapter;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v7.widget.PopupMenu;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@ -16,6 +18,7 @@ import net.schueller.peertube.R;
|
|||||||
import net.schueller.peertube.activity.VideoPlayActivity;
|
import net.schueller.peertube.activity.VideoPlayActivity;
|
||||||
import net.schueller.peertube.helper.APIUrlHelper;
|
import net.schueller.peertube.helper.APIUrlHelper;
|
||||||
import net.schueller.peertube.helper.MetaDataHelper;
|
import net.schueller.peertube.helper.MetaDataHelper;
|
||||||
|
import net.schueller.peertube.intents.Intents;
|
||||||
import net.schueller.peertube.model.Avatar;
|
import net.schueller.peertube.model.Avatar;
|
||||||
import net.schueller.peertube.model.Video;
|
import net.schueller.peertube.model.Video;
|
||||||
|
|
||||||
@ -28,7 +31,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
|||||||
|
|
||||||
private ArrayList<Video> videoList;
|
private ArrayList<Video> videoList;
|
||||||
private Context context;
|
private Context context;
|
||||||
private String apiBaseURL;
|
private String baseUrl;
|
||||||
|
|
||||||
public VideoAdapter(ArrayList<Video> videoList, Context context) {
|
public VideoAdapter(ArrayList<Video> videoList, Context context) {
|
||||||
this.videoList = videoList;
|
this.videoList = videoList;
|
||||||
@ -41,7 +44,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
|||||||
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
View view = layoutInflater.inflate(R.layout.row_video, parent, false);
|
View view = layoutInflater.inflate(R.layout.row_video, parent, false);
|
||||||
|
|
||||||
apiBaseURL = APIUrlHelper.getUrl(context);
|
baseUrl = APIUrlHelper.getUrl(context);
|
||||||
|
|
||||||
return new VideoViewHolder(view);
|
return new VideoViewHolder(view);
|
||||||
}
|
}
|
||||||
@ -50,7 +53,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
|||||||
public void onBindViewHolder(@NonNull VideoViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull VideoViewHolder holder, int position) {
|
||||||
|
|
||||||
Picasso.with(this.context)
|
Picasso.with(this.context)
|
||||||
.load(apiBaseURL + videoList.get(position).getPreviewPath())
|
.load(baseUrl + videoList.get(position).getPreviewPath())
|
||||||
.into(holder.thumb);
|
.into(holder.thumb);
|
||||||
|
|
||||||
|
|
||||||
@ -58,7 +61,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
|||||||
if (avatar != null) {
|
if (avatar != null) {
|
||||||
String avatarPath = avatar.getPath();
|
String avatarPath = avatar.getPath();
|
||||||
Picasso.with(this.context)
|
Picasso.with(this.context)
|
||||||
.load(apiBaseURL + avatarPath)
|
.load(baseUrl + avatarPath)
|
||||||
.into(holder.avatar);
|
.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) {
|
public void setData(ArrayList<Video> data) {
|
||||||
@ -112,6 +132,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
|||||||
|
|
||||||
TextView name, videoMeta, videoOwner;
|
TextView name, videoMeta, videoOwner;
|
||||||
ImageView thumb, avatar;
|
ImageView thumb, avatar;
|
||||||
|
ImageButton moreButton;
|
||||||
View mView;
|
View mView;
|
||||||
|
|
||||||
VideoViewHolder(View itemView) {
|
VideoViewHolder(View itemView) {
|
||||||
@ -121,6 +142,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHol
|
|||||||
avatar = itemView.findViewById(R.id.avatar);
|
avatar = itemView.findViewById(R.id.avatar);
|
||||||
videoMeta = itemView.findViewById(R.id.videoMeta);
|
videoMeta = itemView.findViewById(R.id.videoMeta);
|
||||||
videoOwner = itemView.findViewById(R.id.videoOwner);
|
videoOwner = itemView.findViewById(R.id.videoOwner);
|
||||||
|
moreButton = itemView.findViewById(R.id.moreButton);
|
||||||
mView = itemView;
|
mView = itemView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,4 +23,8 @@ public class APIUrlHelper{
|
|||||||
public static String getUrlWithVersion(Context context) {
|
public static String getUrlWithVersion(Context context) {
|
||||||
return APIUrlHelper.getUrl(context) + "/api/v1/";
|
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:indeterminate="false"
|
||||||
android:max="100" />
|
android:max="100" />
|
||||||
|
|
||||||
<android.support.v7.widget.LinearLayoutCompat
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@id/progress"
|
android:layout_below="@id/progress"
|
||||||
android:padding="16dp"
|
android:padding="6dp"
|
||||||
android:orientation="vertical">
|
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
|
<TextView
|
||||||
android:id="@+id/name"
|
android:id="@+id/name"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
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" />
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/videoMeta"
|
android:id="@+id/videoMeta"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
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" />
|
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
|
<TextView
|
||||||
android:id="@+id/description"
|
android:id="@+id/description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
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:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" />
|
||||||
|
|
||||||
</android.support.v7.widget.LinearLayoutCompat>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
||||||
</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:id="@+id/thumb"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:maxHeight="300dp"
|
|
||||||
android:contentDescription="@string/video_row_video_thumbnail"
|
|
||||||
android:scaleType="fitXY"
|
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
/>
|
android:contentDescription="@string/video_row_video_thumbnail"
|
||||||
|
android:maxHeight="300dp"
|
||||||
|
android:scaleType="fitXY" />
|
||||||
|
|
||||||
<de.hdodenhof.circleimageview.CircleImageView
|
<de.hdodenhof.circleimageview.CircleImageView xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/avatar"
|
android:id="@+id/avatar"
|
||||||
android:layout_width="60dp"
|
android:layout_width="72dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="72dp"
|
||||||
android:paddingTop="12dp"
|
android:layout_below="@+id/thumb"
|
||||||
android:paddingEnd="12dp"
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
android:contentDescription="@string/video_row_account_avatar"
|
android:contentDescription="@string/video_row_account_avatar"
|
||||||
android:layout_below="@id/thumb"
|
android:paddingStart="12dp"
|
||||||
android:layout_alignParentStart="true"/>
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="12dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/name"
|
android:id="@+id/name"
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/thumb"
|
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" />
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/videoMeta"
|
android:id="@+id/videoMeta"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/name"
|
android:layout_below="@+id/name"
|
||||||
android:layout_toEndOf="@id/avatar"
|
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" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -58,8 +65,27 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/videoMeta"
|
android:layout_below="@id/videoMeta"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
android:layout_toEndOf="@id/avatar"
|
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>
|
</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="pref_description_show_nsfw">NSFW content will be shown if enabled.</string>
|
||||||
<string name="title_activity_url_video_play">UrlVideoPlayActivity</string>
|
<string name="title_activity_url_video_play">UrlVideoPlayActivity</string>
|
||||||
<string name="pref_title_torrent_player">Torrent Video Player</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_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_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="pref_title_version">Version</string>
|
||||||
<string name="search_hint">Search PeerTube</string>
|
<string name="search_hint">Search PeerTube</string>
|
||||||
<string name="title_activity_search">Search</string>
|
<string name="title_activity_search">Search</string>
|
||||||
<string name="no_data_available">No Results</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>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user