- Video player moved into service

- Video notification and background playback
This commit is contained in:
Stefan Schueller 2018-12-15 21:59:59 +01:00
parent 6a50094fda
commit a6ce0a570d
10 changed files with 508 additions and 130 deletions

View File

@ -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

View File

@ -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>

View File

@ -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
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() { 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()...");
} }
} }

View File

@ -31,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;
@ -44,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);
} }
@ -53,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);
@ -61,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);
} }

View File

@ -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);
}
}

View 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>

View File

@ -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>

View 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>

View File

@ -26,8 +26,8 @@
<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:id="@+id/avatar"
android:layout_width="60dp" android:layout_width="72dp"
android:layout_height="60dp" android:layout_height="72dp"
android:layout_below="@+id/thumb" android:layout_below="@+id/thumb"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
@ -41,10 +41,10 @@
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_marginStart="12dp" android:layout_marginStart="6dp"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:layout_toEndOf="@+id/avatar" android:layout_toEndOf="@+id/avatar"
android:layout_marginEnd="12dp" android:layout_marginEnd="24dp"
android:paddingTop="0dp" android:paddingTop="0dp"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" /> android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
@ -54,9 +54,9 @@
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_marginStart="12dp" android:layout_marginStart="6dp"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:layout_marginEnd="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" />
@ -65,9 +65,9 @@
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="12dp" android:layout_marginStart="6dp"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:layout_marginEnd="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"
/> />

View File

@ -58,5 +58,6 @@
<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="descr_overflow_button">More</string>
<string name="menu_share">Share</string> <string name="menu_share">Share</string>
<string name="playback_channel_name">PeerTube</string>
</resources> </resources>