Merge branch 'feature-popup' of git://github.com/mauriciocolli/NewPipe
@ -42,10 +42,10 @@ dependencies {
|
||||
compile 'de.hdodenhof:circleimageview:2.0.0'
|
||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||
compile 'com.google.android.exoplayer:exoplayer:r1.5.5'
|
||||
compile 'com.google.code.gson:gson:2.4'
|
||||
compile 'com.nononsenseapps:filepicker:3.0.0'
|
||||
compile 'ch.acra:acra:4.9.0'
|
||||
compile 'com.devbrackets.android:exomedia:3.1.1'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testCompile 'org.json:json:20160810'
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
@ -175,6 +176,66 @@
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".PopupActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:label="NewPipe Popup mode">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="youtube.com" />
|
||||
<data android:host="m.youtube.com" />
|
||||
<data android:host="www.youtube.com" />
|
||||
<!-- video prefix -->
|
||||
<data android:pathPrefix="/v/" />
|
||||
<data android:pathPrefix="/watch" />
|
||||
<data android:pathPrefix="/attribution_link" />
|
||||
<!-- channel prefix -->
|
||||
<data android:pathPrefix="/channel/"/>
|
||||
<data android:pathPrefix="/user/"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="youtu.be" />
|
||||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="vnd.youtube" />
|
||||
<data android:scheme="vnd.youtube.launch" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name=".player.PopupVideoPlayer"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
137
app/src/main/java/org/schabi/newpipe/PopupActivity.java
Normal file
@ -0,0 +1,137 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.util.NavStack;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* This Acitivty is designed to route share/open intents to the specified service, and
|
||||
* to the part of the service which can handle the url.
|
||||
*/
|
||||
|
||||
public class PopupActivity extends Activity {
|
||||
private static final String TAG = RouterActivity.class.toString();
|
||||
|
||||
/**
|
||||
* Removes invisible separators (\p{Z}) and punctuation characters including
|
||||
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
|
||||
* more details.
|
||||
*/
|
||||
private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
handleIntent(getIntent());
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
private static String removeHeadingGibberish(final String input) {
|
||||
int start = 0;
|
||||
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
|
||||
if (!input.substring(i, i + 1).matches("\\p{L}")) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return input.substring(start, input.length());
|
||||
}
|
||||
|
||||
private static String trim(final String input) {
|
||||
if (input == null || input.length() < 1) {
|
||||
return input;
|
||||
} else {
|
||||
String output = input;
|
||||
while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) {
|
||||
output = output.substring(1);
|
||||
}
|
||||
while (output.length() > 0
|
||||
&& output.substring(output.length() - 1, output.length()).matches(REGEX_REMOVE_FROM_URL)) {
|
||||
output = output.substring(0, output.length() - 1);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all Strings which look remotely like URLs from a text.
|
||||
* Used if NewPipe was called through share menu.
|
||||
*
|
||||
* @param sharedText text to scan for URLs.
|
||||
* @return potential URLs
|
||||
*/
|
||||
private String[] getUris(final String sharedText) {
|
||||
final Collection<String> result = new HashSet<>();
|
||||
if (sharedText != null) {
|
||||
final String[] array = sharedText.split("\\p{Space}");
|
||||
for (String s : array) {
|
||||
s = trim(s);
|
||||
if (s.length() != 0) {
|
||||
if (s.matches(".+://.+")) {
|
||||
result.add(removeHeadingGibberish(s));
|
||||
} else if (s.matches(".+\\..+")) {
|
||||
result.add("http://" + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& !PermissionHelper.checkSystemAlertWindowPermission(this)) {
|
||||
Toast.makeText(this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
String videoUrl = "";
|
||||
StreamingService service = null;
|
||||
|
||||
// first gather data and find service
|
||||
if (intent.getData() != null) {
|
||||
// this means the video was called though another app
|
||||
videoUrl = intent.getData().toString();
|
||||
} else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
|
||||
//this means that vidoe was called through share menu
|
||||
String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
videoUrl = getUris(extraText)[0];
|
||||
}
|
||||
|
||||
service = NewPipe.getServiceByUrl(videoUrl);
|
||||
if (service == null) {
|
||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
} else {
|
||||
Intent callIntent = new Intent();
|
||||
switch (service.getLinkTypeByUrl(videoUrl)) {
|
||||
case STREAM:
|
||||
callIntent.setClass(this, PopupVideoPlayer.class);
|
||||
break;
|
||||
case PLAYLIST:
|
||||
Log.e(TAG, "NOT YET DEFINED");
|
||||
break;
|
||||
default:
|
||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
callIntent.putExtra(NavStack.URL, videoUrl);
|
||||
callIntent.putExtra(NavStack.SERVICE_ID, service.getServiceId());
|
||||
startService(callIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,6 +53,7 @@ class ActionBarHandler {
|
||||
// those are edited directly. Typically VideoItemDetailFragment will implement those callbacks.
|
||||
private OnActionListener onShareListener;
|
||||
private OnActionListener onOpenInBrowserListener;
|
||||
private OnActionListener onOpenInPopupListener;
|
||||
private OnActionListener onDownloadListener;
|
||||
private OnActionListener onPlayWithKodiListener;
|
||||
private OnActionListener onPlayAudioListener;
|
||||
@ -190,6 +191,12 @@ class ActionBarHandler {
|
||||
activity.startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_item_popup: {
|
||||
if(onOpenInPopupListener != null) {
|
||||
onOpenInPopupListener.onActionSelected(selectedVideoStream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
Log.e(TAG, "Menu Item not known");
|
||||
}
|
||||
@ -208,6 +215,10 @@ class ActionBarHandler {
|
||||
onOpenInBrowserListener = listener;
|
||||
}
|
||||
|
||||
public void setOnOpenInPopupListener(OnActionListener listener) {
|
||||
onOpenInPopupListener = listener;
|
||||
}
|
||||
|
||||
public void setOnDownloadListener(OnActionListener listener) {
|
||||
onDownloadListener = listener;
|
||||
}
|
||||
|
||||
@ -33,8 +33,6 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
@ -56,12 +54,13 @@ import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.ExoPlayerActivity;
|
||||
import org.schabi.newpipe.player.PlayVideoActivity;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.schabi.newpipe.util.NavStack;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
|
||||
|
||||
@ -324,6 +323,19 @@ public class VideoItemDetailFragment extends Fragment {
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
streamThumbnail = loadedImage;
|
||||
|
||||
if (streamThumbnail != null) {
|
||||
// TODO: Change the thumbnail implementation
|
||||
|
||||
// When the thumbnail is not loaded yet, it not passes to the service in time
|
||||
// so, I can notify the service through a broadcast, but the problem is
|
||||
// when I click in another video, another thumbnail will be load, and will
|
||||
// notify again, so I send the videoUrl and compare with the service's url
|
||||
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
|
||||
Intent intent = new Intent(PopupVideoPlayer.InternalListener.ACTION_UPDATE_THUMB);
|
||||
intent.putExtra(PopupVideoPlayer.VIDEO_URL, info.webpage_url);
|
||||
getContext().sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -365,6 +377,28 @@ public class VideoItemDetailFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
actionBarHandler.setOnOpenInPopupListener(new ActionBarHandler.OnActionListener() {
|
||||
@Override
|
||||
public void onActionSelected(int selectedStreamId) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& !PermissionHelper.checkSystemAlertWindowPermission(activity)) {
|
||||
Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
if (streamThumbnail != null)
|
||||
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
|
||||
|
||||
VideoStream selectedVideoStream = info.video_streams.get(selectedStreamId);
|
||||
Intent i = new Intent(activity, PopupVideoPlayer.class);
|
||||
Toast.makeText(activity, "Starting in popup mode", Toast.LENGTH_SHORT).show();
|
||||
i.putExtra(PopupVideoPlayer.VIDEO_TITLE, info.title)
|
||||
.putExtra(PopupVideoPlayer.STREAM_URL, selectedVideoStream.url)
|
||||
.putExtra(PopupVideoPlayer.CHANNEL_NAME, info.uploader)
|
||||
.putExtra(PopupVideoPlayer.VIDEO_URL, info.webpage_url);
|
||||
activity.startService(i);
|
||||
}
|
||||
});
|
||||
|
||||
actionBarHandler.setOnPlayWithKodiListener(new ActionBarHandler.OnActionListener() {
|
||||
@Override
|
||||
public void onActionSelected(int selectedStreamId) {
|
||||
@ -753,13 +787,16 @@ public class VideoItemDetailFragment extends Fragment {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getBoolean(activity.getString(R.string.use_exoplayer_key), false)) {
|
||||
|
||||
// TODO: Fix this mess
|
||||
if (streamThumbnail != null)
|
||||
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
|
||||
// exo player
|
||||
|
||||
if(info.dashMpdUrl != null && !info.dashMpdUrl.isEmpty()) {
|
||||
// try dash
|
||||
Intent intent = new Intent(activity, ExoPlayerActivity.class)
|
||||
.setData(Uri.parse(info.dashMpdUrl))
|
||||
.putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_DASH);
|
||||
.setData(Uri.parse(info.dashMpdUrl));
|
||||
//.putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_DASH);
|
||||
startActivity(intent);
|
||||
} else if((info.audio_streams != null && !info.audio_streams.isEmpty()) &&
|
||||
(info.video_only_streams != null && !info.video_only_streams.isEmpty())) {
|
||||
@ -770,7 +807,10 @@ public class VideoItemDetailFragment extends Fragment {
|
||||
Intent intent = new Intent(activity, ExoPlayerActivity.class)
|
||||
.setDataAndType(Uri.parse(selectedVideoStream.url),
|
||||
MediaFormat.getMimeById(selectedVideoStream.format))
|
||||
.putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_OTHER);
|
||||
|
||||
.putExtra(ExoPlayerActivity.VIDEO_TITLE, info.title)
|
||||
.putExtra(ExoPlayerActivity.CHANNEL_NAME, info.uploader);
|
||||
//.putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_OTHER);
|
||||
|
||||
activity.startActivity(intent); // HERE !!!
|
||||
}
|
||||
|
||||
@ -1,268 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.schabi.newpipe.player.exoplayer;
|
||||
|
||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
||||
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecSelector;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||
import com.google.android.exoplayer.dash.DashChunkSource;
|
||||
import com.google.android.exoplayer.dash.DefaultDashTrackSelector;
|
||||
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
|
||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
|
||||
import com.google.android.exoplayer.dash.mpd.Period;
|
||||
import com.google.android.exoplayer.dash.mpd.UtcTimingElement;
|
||||
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver;
|
||||
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback;
|
||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||
import com.google.android.exoplayer.drm.UnsupportedDrmException;
|
||||
import com.google.android.exoplayer.text.TextTrackRenderer;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
||||
import com.google.android.exoplayer.upstream.UriDataSource;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaCodec;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A {@link RendererBuilder} for DASH.
|
||||
*/
|
||||
public class DashRendererBuilder implements RendererBuilder {
|
||||
|
||||
private static final String TAG = "DashRendererBuilder";
|
||||
|
||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
||||
private static final int AUDIO_BUFFER_SEGMENTS = 54;
|
||||
private static final int TEXT_BUFFER_SEGMENTS = 2;
|
||||
private static final int LIVE_EDGE_LATENCY_MS = 30000;
|
||||
|
||||
private static final int SECURITY_LEVEL_UNKNOWN = -1;
|
||||
private static final int SECURITY_LEVEL_1 = 1;
|
||||
private static final int SECURITY_LEVEL_3 = 3;
|
||||
|
||||
private final Context context;
|
||||
private final String userAgent;
|
||||
private final String url;
|
||||
private final MediaDrmCallback drmCallback;
|
||||
|
||||
private AsyncRendererBuilder currentAsyncBuilder;
|
||||
|
||||
public DashRendererBuilder(Context context, String userAgent, String url,
|
||||
MediaDrmCallback drmCallback) {
|
||||
this.context = context;
|
||||
this.userAgent = userAgent;
|
||||
this.url = url;
|
||||
this.drmCallback = drmCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRenderers(NPExoPlayer player) {
|
||||
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player);
|
||||
currentAsyncBuilder.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (currentAsyncBuilder != null) {
|
||||
currentAsyncBuilder.cancel();
|
||||
currentAsyncBuilder = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AsyncRendererBuilder
|
||||
implements ManifestFetcher.ManifestCallback<MediaPresentationDescription>, UtcTimingCallback {
|
||||
|
||||
private final Context context;
|
||||
private final String userAgent;
|
||||
private final MediaDrmCallback drmCallback;
|
||||
private final NPExoPlayer player;
|
||||
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
||||
private final UriDataSource manifestDataSource;
|
||||
|
||||
private boolean canceled;
|
||||
private MediaPresentationDescription manifest;
|
||||
private long elapsedRealtimeOffset;
|
||||
|
||||
public AsyncRendererBuilder(Context context, String userAgent, String url,
|
||||
MediaDrmCallback drmCallback, NPExoPlayer player) {
|
||||
this.context = context;
|
||||
this.userAgent = userAgent;
|
||||
this.drmCallback = drmCallback;
|
||||
this.player = player;
|
||||
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
||||
manifestDataSource = new DefaultUriDataSource(context, userAgent);
|
||||
manifestFetcher = new ManifestFetcher<>(url, manifestDataSource, parser);
|
||||
}
|
||||
|
||||
public void init() {
|
||||
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
canceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleManifest(MediaPresentationDescription manifest) {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.manifest = manifest;
|
||||
if (manifest.dynamic && manifest.utcTiming != null) {
|
||||
UtcTimingElementResolver.resolveTimingElement(manifestDataSource, manifest.utcTiming,
|
||||
manifestFetcher.getManifestLoadCompleteTimestamp(), this);
|
||||
} else {
|
||||
buildRenderers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleManifestError(IOException e) {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.onRenderersError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
|
||||
buildRenderers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimestampError(UtcTimingElement utcTiming, IOException e) {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e);
|
||||
// Be optimistic and continue in the hope that the device clock is correct.
|
||||
buildRenderers();
|
||||
}
|
||||
|
||||
private void buildRenderers() {
|
||||
Period period = manifest.getPeriod(0);
|
||||
Handler mainHandler = player.getMainHandler();
|
||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);
|
||||
|
||||
boolean hasContentProtection = false;
|
||||
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
||||
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
||||
if (adaptationSet.type != AdaptationSet.TYPE_UNKNOWN) {
|
||||
hasContentProtection |= adaptationSet.hasContentProtection();
|
||||
}
|
||||
}
|
||||
|
||||
// Check drm support if necessary.
|
||||
boolean filterHdContent = false;
|
||||
StreamingDrmSessionManager drmSessionManager = null;
|
||||
if (hasContentProtection) {
|
||||
if (Util.SDK_INT < 18) {
|
||||
player.onRenderersError(
|
||||
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
drmSessionManager = StreamingDrmSessionManager.newWidevineInstance(
|
||||
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
|
||||
filterHdContent = getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1;
|
||||
} catch (UnsupportedDrmException e) {
|
||||
player.onRenderersError(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher,
|
||||
DefaultDashTrackSelector.newVideoInstance(context, true, filterHdContent),
|
||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS,
|
||||
elapsedRealtimeOffset, mainHandler, player, NPExoPlayer.TYPE_VIDEO);
|
||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
NPExoPlayer.TYPE_VIDEO);
|
||||
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
|
||||
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000,
|
||||
drmSessionManager, true, mainHandler, player, 50);
|
||||
|
||||
// Build the audio renderer.
|
||||
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher,
|
||||
DefaultDashTrackSelector.newAudioInstance(), audioDataSource, null, LIVE_EDGE_LATENCY_MS,
|
||||
elapsedRealtimeOffset, mainHandler, player, NPExoPlayer.TYPE_AUDIO);
|
||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
NPExoPlayer.TYPE_AUDIO);
|
||||
TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource,
|
||||
MediaCodecSelector.DEFAULT, drmSessionManager, true, mainHandler, player,
|
||||
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
|
||||
|
||||
// Build the text renderer.
|
||||
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher,
|
||||
DefaultDashTrackSelector.newTextInstance(), textDataSource, null, LIVE_EDGE_LATENCY_MS,
|
||||
elapsedRealtimeOffset, mainHandler, player, NPExoPlayer.TYPE_TEXT);
|
||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
NPExoPlayer.TYPE_TEXT);
|
||||
TrackRenderer textRenderer = new TextTrackRenderer(textSampleSource, player,
|
||||
mainHandler.getLooper());
|
||||
|
||||
// Invoke the callback.
|
||||
TrackRenderer[] renderers = new TrackRenderer[NPExoPlayer.RENDERER_COUNT];
|
||||
renderers[NPExoPlayer.TYPE_VIDEO] = videoRenderer;
|
||||
renderers[NPExoPlayer.TYPE_AUDIO] = audioRenderer;
|
||||
renderers[NPExoPlayer.TYPE_TEXT] = textRenderer;
|
||||
player.onRenderers(renderers, bandwidthMeter);
|
||||
}
|
||||
|
||||
private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) {
|
||||
String securityLevelProperty = sessionManager.getPropertyString("securityLevel");
|
||||
return securityLevelProperty.equals("L1") ? SECURITY_LEVEL_1 : securityLevelProperty
|
||||
.equals("L3") ? SECURITY_LEVEL_3 : SECURITY_LEVEL_UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.schabi.newpipe.player.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||
import com.google.android.exoplayer.TimeRange;
|
||||
import com.google.android.exoplayer.audio.AudioTrack;
|
||||
import com.google.android.exoplayer.chunk.Format;
|
||||
import com.google.android.exoplayer.util.VerboseLogUtil;
|
||||
|
||||
import android.media.MediaCodec.CryptoException;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Logs player events using {@link Log}.
|
||||
*/
|
||||
public class EventLogger implements NPExoPlayer.Listener, NPExoPlayer.InfoListener,
|
||||
NPExoPlayer.InternalErrorListener {
|
||||
|
||||
private static final String TAG = "EventLogger";
|
||||
private static final NumberFormat TIME_FORMAT;
|
||||
static {
|
||||
TIME_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
TIME_FORMAT.setMinimumFractionDigits(2);
|
||||
TIME_FORMAT.setMaximumFractionDigits(2);
|
||||
}
|
||||
|
||||
private long sessionStartTimeMs;
|
||||
private long[] loadStartTimeMs;
|
||||
private long[] availableRangeValuesUs;
|
||||
|
||||
public EventLogger() {
|
||||
loadStartTimeMs = new long[NPExoPlayer.RENDERER_COUNT];
|
||||
}
|
||||
|
||||
public void startSession() {
|
||||
sessionStartTimeMs = SystemClock.elapsedRealtime();
|
||||
Log.d(TAG, "start [0]");
|
||||
}
|
||||
|
||||
public void endSession() {
|
||||
Log.d(TAG, "end [" + getSessionTimeString() + "]");
|
||||
}
|
||||
|
||||
// NPExoPlayer.Listener
|
||||
|
||||
@Override
|
||||
public void onStateChanged(boolean playWhenReady, int state) {
|
||||
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", "
|
||||
+ getStateString(state) + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio) {
|
||||
Log.d(TAG, "videoSizeChanged [" + width + ", " + height + ", " + unappliedRotationDegrees
|
||||
+ ", " + pixelWidthHeightRatio + "]");
|
||||
}
|
||||
|
||||
// NPExoPlayer.InfoListener
|
||||
|
||||
@Override
|
||||
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) {
|
||||
Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes + ", "
|
||||
+ getTimeString(elapsedMs) + ", " + bitrateEstimate + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDroppedFrames(int count, long elapsed) {
|
||||
Log.d(TAG, "droppedFrames [" + getSessionTimeString() + ", " + count + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
|
||||
long mediaStartTimeMs, long mediaEndTimeMs) {
|
||||
loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime();
|
||||
if (VerboseLogUtil.isTagEnabled(TAG)) {
|
||||
Log.v(TAG, "loadStart [" + getSessionTimeString() + ", " + sourceId + ", " + type
|
||||
+ ", " + mediaStartTimeMs + ", " + mediaEndTimeMs + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
|
||||
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs) {
|
||||
if (VerboseLogUtil.isTagEnabled(TAG)) {
|
||||
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId];
|
||||
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " + downloadTime
|
||||
+ "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs) {
|
||||
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + format.id + ", "
|
||||
+ Integer.toString(trigger) + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs) {
|
||||
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + format.id + ", "
|
||||
+ Integer.toString(trigger) + "]");
|
||||
}
|
||||
|
||||
// NPExoPlayer.InternalErrorListener
|
||||
|
||||
@Override
|
||||
public void onLoadError(int sourceId, IOException e) {
|
||||
printInternalError("loadError", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRendererInitializationError(Exception e) {
|
||||
printInternalError("rendererInitError", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrmSessionManagerError(Exception e) {
|
||||
printInternalError("drmSessionManagerError", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDecoderInitializationError(DecoderInitializationException e) {
|
||||
printInternalError("decoderInitializationError", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
|
||||
printInternalError("audioTrackInitializationError", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioTrackWriteError(AudioTrack.WriteException e) {
|
||||
printInternalError("audioTrackWriteError", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||
printInternalError("audioTrackUnderrun [" + bufferSize + ", " + bufferSizeMs + ", "
|
||||
+ elapsedSinceLastFeedMs + "]", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoError(CryptoException e) {
|
||||
printInternalError("cryptoError", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
|
||||
long initializationDurationMs) {
|
||||
Log.d(TAG, "decoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) {
|
||||
availableRangeValuesUs = availableRange.getCurrentBoundsUs(availableRangeValuesUs);
|
||||
Log.d(TAG, "availableRange [" + availableRange.isStatic() + ", " + availableRangeValuesUs[0]
|
||||
+ ", " + availableRangeValuesUs[1] + "]");
|
||||
}
|
||||
|
||||
private void printInternalError(String type, Exception e) {
|
||||
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
|
||||
}
|
||||
|
||||
private String getStateString(int state) {
|
||||
switch (state) {
|
||||
case ExoPlayer.STATE_BUFFERING:
|
||||
return "B";
|
||||
case ExoPlayer.STATE_ENDED:
|
||||
return "E";
|
||||
case ExoPlayer.STATE_IDLE:
|
||||
return "I";
|
||||
case ExoPlayer.STATE_PREPARING:
|
||||
return "P";
|
||||
case ExoPlayer.STATE_READY:
|
||||
return "R";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
private String getSessionTimeString() {
|
||||
return getTimeString(SystemClock.elapsedRealtime() - sessionStartTimeMs);
|
||||
}
|
||||
|
||||
private String getTimeString(long timeMs) {
|
||||
return TIME_FORMAT.format((timeMs) / 1000f);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.schabi.newpipe.player.exoplayer;
|
||||
|
||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
||||
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecSelector;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
||||
import com.google.android.exoplayer.text.TextTrackRenderer;
|
||||
import com.google.android.exoplayer.upstream.Allocator;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaCodec;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* A {@link RendererBuilder} for streams that can be read using an {@link Extractor}.
|
||||
*/
|
||||
public class ExtractorRendererBuilder implements RendererBuilder {
|
||||
|
||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||
private static final int BUFFER_SEGMENT_COUNT = 256;
|
||||
|
||||
private final Context context;
|
||||
private final String userAgent;
|
||||
private final Uri uri;
|
||||
|
||||
public ExtractorRendererBuilder(Context context, String userAgent, Uri uri) {
|
||||
this.context = context;
|
||||
this.userAgent = userAgent;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRenderers(NPExoPlayer player) {
|
||||
Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);
|
||||
|
||||
// Build the video and audio renderers.
|
||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(player.getMainHandler(),
|
||||
null);
|
||||
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator,
|
||||
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
|
||||
sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000,
|
||||
player.getMainHandler(), player, 50);
|
||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
|
||||
MediaCodecSelector.DEFAULT, null, true, player.getMainHandler(), player,
|
||||
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
|
||||
TrackRenderer textRenderer = new TextTrackRenderer(sampleSource, player,
|
||||
player.getMainHandler().getLooper());
|
||||
|
||||
// Invoke the callback.
|
||||
TrackRenderer[] renderers = new TrackRenderer[NPExoPlayer.RENDERER_COUNT];
|
||||
renderers[NPExoPlayer.TYPE_VIDEO] = videoRenderer;
|
||||
renderers[NPExoPlayer.TYPE_AUDIO] = audioRenderer;
|
||||
renderers[NPExoPlayer.TYPE_TEXT] = textRenderer;
|
||||
player.onRenderers(renderers, bandwidthMeter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.schabi.newpipe.player.exoplayer;
|
||||
|
||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
||||
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecSelector;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||
import com.google.android.exoplayer.hls.DefaultHlsTrackSelector;
|
||||
import com.google.android.exoplayer.hls.HlsChunkSource;
|
||||
import com.google.android.exoplayer.hls.HlsMasterPlaylist;
|
||||
import com.google.android.exoplayer.hls.HlsPlaylist;
|
||||
import com.google.android.exoplayer.hls.HlsPlaylistParser;
|
||||
import com.google.android.exoplayer.hls.HlsSampleSource;
|
||||
import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider;
|
||||
import com.google.android.exoplayer.metadata.Id3Parser;
|
||||
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
|
||||
import com.google.android.exoplayer.text.TextTrackRenderer;
|
||||
import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaCodec;
|
||||
import android.os.Handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link RendererBuilder} for HLS.
|
||||
*/
|
||||
public class HlsRendererBuilder implements RendererBuilder {
|
||||
|
||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||
private static final int MAIN_BUFFER_SEGMENTS = 256;
|
||||
private static final int TEXT_BUFFER_SEGMENTS = 2;
|
||||
|
||||
private final Context context;
|
||||
private final String userAgent;
|
||||
private final String url;
|
||||
|
||||
private AsyncRendererBuilder currentAsyncBuilder;
|
||||
|
||||
public HlsRendererBuilder(Context context, String userAgent, String url) {
|
||||
this.context = context;
|
||||
this.userAgent = userAgent;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRenderers(NPExoPlayer player) {
|
||||
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, player);
|
||||
currentAsyncBuilder.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (currentAsyncBuilder != null) {
|
||||
currentAsyncBuilder.cancel();
|
||||
currentAsyncBuilder = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AsyncRendererBuilder implements ManifestCallback<HlsPlaylist> {
|
||||
|
||||
private final Context context;
|
||||
private final String userAgent;
|
||||
private final String url;
|
||||
private final NPExoPlayer player;
|
||||
private final ManifestFetcher<HlsPlaylist> playlistFetcher;
|
||||
|
||||
private boolean canceled;
|
||||
|
||||
public AsyncRendererBuilder(Context context, String userAgent, String url, NPExoPlayer player) {
|
||||
this.context = context;
|
||||
this.userAgent = userAgent;
|
||||
this.url = url;
|
||||
this.player = player;
|
||||
HlsPlaylistParser parser = new HlsPlaylistParser();
|
||||
playlistFetcher = new ManifestFetcher<>(url, new DefaultUriDataSource(context, userAgent),
|
||||
parser);
|
||||
}
|
||||
|
||||
public void init() {
|
||||
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
canceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleManifestError(IOException e) {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.onRenderersError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleManifest(HlsPlaylist manifest) {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Handler mainHandler = player.getMainHandler();
|
||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
|
||||
|
||||
// Build the video/audio/metadata renderers.
|
||||
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
HlsChunkSource chunkSource = new HlsChunkSource(true /* isMaster */, dataSource, url,
|
||||
manifest, DefaultHlsTrackSelector.newDefaultInstance(context), bandwidthMeter,
|
||||
timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
||||
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
|
||||
MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, NPExoPlayer.TYPE_VIDEO);
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
|
||||
sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
|
||||
5000, mainHandler, player, 50);
|
||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
|
||||
MediaCodecSelector.DEFAULT, null, true, player.getMainHandler(), player,
|
||||
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
|
||||
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
|
||||
sampleSource, new Id3Parser(), player, mainHandler.getLooper());
|
||||
|
||||
// Build the text renderer, preferring Webvtt where available.
|
||||
boolean preferWebvtt = false;
|
||||
if (manifest instanceof HlsMasterPlaylist) {
|
||||
preferWebvtt = !((HlsMasterPlaylist) manifest).subtitles.isEmpty();
|
||||
}
|
||||
TrackRenderer textRenderer;
|
||||
if (preferWebvtt) {
|
||||
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
HlsChunkSource textChunkSource = new HlsChunkSource(false /* isMaster */, textDataSource,
|
||||
url, manifest, DefaultHlsTrackSelector.newVttInstance(), bandwidthMeter,
|
||||
timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
||||
HlsSampleSource textSampleSource = new HlsSampleSource(textChunkSource, loadControl,
|
||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, NPExoPlayer.TYPE_TEXT);
|
||||
textRenderer = new TextTrackRenderer(textSampleSource, player, mainHandler.getLooper());
|
||||
} else {
|
||||
textRenderer = new Eia608TrackRenderer(sampleSource, player, mainHandler.getLooper());
|
||||
}
|
||||
|
||||
TrackRenderer[] renderers = new TrackRenderer[NPExoPlayer.RENDERER_COUNT];
|
||||
renderers[NPExoPlayer.TYPE_VIDEO] = videoRenderer;
|
||||
renderers[NPExoPlayer.TYPE_AUDIO] = audioRenderer;
|
||||
renderers[NPExoPlayer.TYPE_METADATA] = id3Renderer;
|
||||
renderers[NPExoPlayer.TYPE_TEXT] = textRenderer;
|
||||
player.onRenderers(renderers, bandwidthMeter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.schabi.newpipe.player.exoplayer;
|
||||
|
||||
|
||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
||||
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecSelector;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||
import com.google.android.exoplayer.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||
import com.google.android.exoplayer.drm.UnsupportedDrmException;
|
||||
import com.google.android.exoplayer.smoothstreaming.DefaultSmoothStreamingTrackSelector;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser;
|
||||
import com.google.android.exoplayer.text.TextTrackRenderer;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaCodec;
|
||||
import android.os.Handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A {@link RendererBuilder} for SmoothStreaming.
|
||||
*/
|
||||
public class SmoothStreamingRendererBuilder implements RendererBuilder {
|
||||
|
||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
||||
private static final int AUDIO_BUFFER_SEGMENTS = 54;
|
||||
private static final int TEXT_BUFFER_SEGMENTS = 2;
|
||||
private static final int LIVE_EDGE_LATENCY_MS = 30000;
|
||||
|
||||
private final Context context;
|
||||
private final String userAgent;
|
||||
private final String url;
|
||||
private final MediaDrmCallback drmCallback;
|
||||
|
||||
private AsyncRendererBuilder currentAsyncBuilder;
|
||||
|
||||
public SmoothStreamingRendererBuilder(Context context, String userAgent, String url,
|
||||
MediaDrmCallback drmCallback) {
|
||||
this.context = context;
|
||||
this.userAgent = userAgent;
|
||||
this.url = Util.toLowerInvariant(url).endsWith("/manifest") ? url : url + "/Manifest";
|
||||
this.drmCallback = drmCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRenderers(NPExoPlayer player) {
|
||||
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player);
|
||||
currentAsyncBuilder.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (currentAsyncBuilder != null) {
|
||||
currentAsyncBuilder.cancel();
|
||||
currentAsyncBuilder = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AsyncRendererBuilder
|
||||
implements ManifestFetcher.ManifestCallback<SmoothStreamingManifest> {
|
||||
|
||||
private final Context context;
|
||||
private final String userAgent;
|
||||
private final MediaDrmCallback drmCallback;
|
||||
private final NPExoPlayer player;
|
||||
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
|
||||
|
||||
private boolean canceled;
|
||||
|
||||
public AsyncRendererBuilder(Context context, String userAgent, String url,
|
||||
MediaDrmCallback drmCallback, NPExoPlayer player) {
|
||||
this.context = context;
|
||||
this.userAgent = userAgent;
|
||||
this.drmCallback = drmCallback;
|
||||
this.player = player;
|
||||
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
|
||||
manifestFetcher = new ManifestFetcher<>(url, new DefaultHttpDataSource(userAgent, null),
|
||||
parser);
|
||||
}
|
||||
|
||||
public void init() {
|
||||
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
canceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleManifestError(IOException exception) {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.onRenderersError(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleManifest(SmoothStreamingManifest manifest) {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Handler mainHandler = player.getMainHandler();
|
||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);
|
||||
|
||||
// Check drm support if necessary.
|
||||
DrmSessionManager drmSessionManager = null;
|
||||
if (manifest.protectionElement != null) {
|
||||
if (Util.SDK_INT < 18) {
|
||||
player.onRenderersError(
|
||||
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
drmSessionManager = new StreamingDrmSessionManager(manifest.protectionElement.uuid,
|
||||
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
|
||||
} catch (UnsupportedDrmException e) {
|
||||
player.onRenderersError(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||
DefaultSmoothStreamingTrackSelector.newVideoInstance(context, true, false),
|
||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
|
||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
NPExoPlayer.TYPE_VIDEO);
|
||||
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
|
||||
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000,
|
||||
drmSessionManager, true, mainHandler, player, 50);
|
||||
|
||||
// Build the audio renderer.
|
||||
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||
DefaultSmoothStreamingTrackSelector.newAudioInstance(),
|
||||
audioDataSource, null, LIVE_EDGE_LATENCY_MS);
|
||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
NPExoPlayer.TYPE_AUDIO);
|
||||
TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource,
|
||||
MediaCodecSelector.DEFAULT, drmSessionManager, true, mainHandler, player,
|
||||
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
|
||||
|
||||
// Build the text renderer.
|
||||
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||
DefaultSmoothStreamingTrackSelector.newTextInstance(),
|
||||
textDataSource, null, LIVE_EDGE_LATENCY_MS);
|
||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
NPExoPlayer.TYPE_TEXT);
|
||||
TrackRenderer textRenderer = new TextTrackRenderer(textSampleSource, player,
|
||||
mainHandler.getLooper());
|
||||
|
||||
// Invoke the callback.
|
||||
TrackRenderer[] renderers = new TrackRenderer[NPExoPlayer.RENDERER_COUNT];
|
||||
renderers[NPExoPlayer.TYPE_VIDEO] = videoRenderer;
|
||||
renderers[NPExoPlayer.TYPE_AUDIO] = audioRenderer;
|
||||
renderers[NPExoPlayer.TYPE_TEXT] = textRenderer;
|
||||
player.onRenderers(renderers, bandwidthMeter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
package org.schabi.newpipe.player.popup;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.devbrackets.android.exomedia.ui.widget.EMVideoView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class PopupViewHolder {
|
||||
private View rootView;
|
||||
private EMVideoView videoView;
|
||||
private View loadingPanel;
|
||||
private ImageView endScreen;
|
||||
private ImageView controlAnimationView;
|
||||
private LinearLayout controlsRoot;
|
||||
private SeekBar playbackSeekBar;
|
||||
private TextView playbackCurrentTime;
|
||||
private TextView playbackEndTime;
|
||||
|
||||
public PopupViewHolder(View rootView) {
|
||||
if (rootView == null) return;
|
||||
this.rootView = rootView;
|
||||
this.videoView = (EMVideoView) rootView.findViewById(R.id.popupVideoView);
|
||||
this.loadingPanel = rootView.findViewById(R.id.loadingPanel);
|
||||
this.endScreen = (ImageView) rootView.findViewById(R.id.endScreen);
|
||||
this.controlAnimationView = (ImageView) rootView.findViewById(R.id.controlAnimationView);
|
||||
this.controlsRoot = (LinearLayout) rootView.findViewById(R.id.playbackControlRoot);
|
||||
this.playbackSeekBar = (SeekBar) rootView.findViewById(R.id.playbackSeekBar);
|
||||
this.playbackCurrentTime = (TextView) rootView.findViewById(R.id.playbackCurrentTime);
|
||||
this.playbackEndTime = (TextView) rootView.findViewById(R.id.playbackEndTime);
|
||||
doModifications();
|
||||
}
|
||||
|
||||
private void doModifications() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
||||
playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
|
||||
}
|
||||
|
||||
public boolean isControlsVisible() {
|
||||
return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
public boolean isVisible(View view) {
|
||||
return view != null && view.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// GETTERS
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public View getRootView() {
|
||||
return rootView;
|
||||
}
|
||||
|
||||
public EMVideoView getVideoView() {
|
||||
return videoView;
|
||||
}
|
||||
|
||||
public View getLoadingPanel() {
|
||||
return loadingPanel;
|
||||
}
|
||||
|
||||
public ImageView getEndScreen() {
|
||||
return endScreen;
|
||||
}
|
||||
|
||||
public ImageView getControlAnimationView() {
|
||||
return controlAnimationView;
|
||||
}
|
||||
|
||||
public LinearLayout getControlsRoot() {
|
||||
return controlsRoot;
|
||||
}
|
||||
|
||||
public SeekBar getPlaybackSeekBar() {
|
||||
return playbackSeekBar;
|
||||
}
|
||||
|
||||
public TextView getPlaybackCurrentTime() {
|
||||
return playbackCurrentTime;
|
||||
}
|
||||
|
||||
public TextView getPlaybackEndTime() {
|
||||
return playbackEndTime;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.schabi.newpipe.player.popup;
|
||||
|
||||
public interface StateInterface {
|
||||
int STATE_LOADING = 123;
|
||||
int STATE_PLAYING = 125;
|
||||
int STATE_PAUSED = 126;
|
||||
int STATE_PAUSED_SEEK = 127;
|
||||
int STATE_COMPLETED = 128;
|
||||
|
||||
void changeState(int state);
|
||||
|
||||
void onLoading();
|
||||
void onPlaying();
|
||||
void onPaused();
|
||||
void onPausedSeek();
|
||||
void onCompleted();
|
||||
}
|
||||
@ -2,8 +2,12 @@ package org.schabi.newpipe.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
@ -11,7 +15,7 @@ import android.support.v4.content.ContextCompat;
|
||||
public class PermissionHelper {
|
||||
public static final int PERMISSION_WRITE_STORAGE = 778;
|
||||
public static final int PERMISSION_READ_STORAGE = 777;
|
||||
|
||||
public static final int PERMISSION_SYSTEM_ALERT_WINDOW = 779;
|
||||
|
||||
|
||||
public static boolean checkStoragePermissions(Activity activity) {
|
||||
@ -65,4 +69,27 @@ public class PermissionHelper {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* In order to be able to draw over other apps, the permission android.permission.SYSTEM_ALERT_WINDOW have to be granted.
|
||||
* <p>
|
||||
* On < API 23 (MarshMallow) the permission was granted when the user installed the application (via AndroidManifest),
|
||||
* on > 23, however, it have to start a activity asking the user if he agree.
|
||||
* <p>
|
||||
* This method just return if canDraw over other apps, if it doesn't, try to get the permission,
|
||||
* it does not get the result of the startActivityForResult, if the user accept, the next time that he tries to open
|
||||
* it will return true.
|
||||
*
|
||||
* @param activity context to startActivityForResult
|
||||
* @return returns {@link Settings#canDrawOverlays(Context)}
|
||||
**/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public static boolean checkSystemAlertWindowPermission(Activity activity) {
|
||||
if (!Settings.canDrawOverlays(activity)) {
|
||||
Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + activity.getPackageName()));
|
||||
activity.startActivityForResult(i, PERMISSION_SYSTEM_ALERT_WINDOW);
|
||||
return false;
|
||||
}else return true;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
app/src/main/res/drawable-hdpi/ic_action_av_fast_forward.png
Normal file
|
After Width: | Height: | Size: 575 B |
BIN
app/src/main/res/drawable-hdpi/ic_replay_white.png
Normal file
|
After Width: | Height: | Size: 675 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_av_fast_forward.png
Normal file
|
After Width: | Height: | Size: 277 B |
BIN
app/src/main/res/drawable-mdpi/ic_replay_white.png
Normal file
|
After Width: | Height: | Size: 457 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_av_fast_forward.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
app/src/main/res/drawable-xhdpi/ic_replay_white.png
Normal file
|
After Width: | Height: | Size: 908 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_av_fast_forward.png
Normal file
|
After Width: | Height: | Size: 889 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_replay_white.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_av_fast_forward.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_replay_white.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
9
app/src/main/res/drawable/popup_controls_bg.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:startColor="#a0000000"
|
||||
android:centerColor="#26000000"
|
||||
android:endColor="#00000000"
|
||||
android:angle="90"
|
||||
/>
|
||||
</shape>
|
||||
15
app/src/main/res/layout/activity_exo_player.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/root"
|
||||
android:focusable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<com.devbrackets.android.exomedia.ui.widget.EMVideoView
|
||||
android:id="@+id/emVideoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/root"
|
||||
android:focusable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<com.google.android.exoplayer.AspectRatioFrameLayout android:id="@+id/video_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<SurfaceView android:id="@+id/surface_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<View android:id="@+id/shutter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"/>
|
||||
|
||||
<com.google.android.exoplayer.text.SubtitleLayout android:id="@+id/subtitles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</com.google.android.exoplayer.AspectRatioFrameLayout>
|
||||
|
||||
</FrameLayout>
|
||||
180
app/src/main/res/layout/exomedia_custom_controls.xml
Normal file
@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.devbrackets.android.exomedia.ui.widget.FitsSystemWindowRelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/exomedia_controls_text_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="@drawable/exomedia_default_controls_text_background"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exomedia_controls_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="The Video Title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exomedia_controls_sub_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
tools:text="The Video Album"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exomedia_controls_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
tools:text="The Video Artist"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/exomedia_controls_interactive_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:background="@drawable/exomedia_default_controls_interactive_background"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingTop="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exomedia_controls_current_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingRight="2dp"
|
||||
android:textColor="@android:color/white"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="1:06:29"/>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/exomedia_controls_video_seek"
|
||||
style="?android:seekBarStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
tools:ignore="RtlHardcoded"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exomedia_controls_end_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingRight="2dp"
|
||||
android:textColor="@android:color/white"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="1:23:49"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/exomedia_controls_extra_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="RtlHardcoded,UselessLeaf"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/exomedia_controls_previous_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:minHeight="24dp"
|
||||
android:minWidth="24dp"
|
||||
app:srcCompat="@drawable/exomedia_ic_skip_previous_white"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/exomedia_controls_frewind_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:minHeight="24dp"
|
||||
android:minWidth="24dp"
|
||||
android:src="@drawable/exomedia_ic_rewind_white"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/exomedia_controls_play_pause_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:minHeight="24dp"
|
||||
android:minWidth="24dp"
|
||||
app:srcCompat="@drawable/exomedia_ic_play_arrow_white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/exomedia_controls_fforward_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:minHeight="24dp"
|
||||
android:minWidth="24dp"
|
||||
android:src="@drawable/exomedia_ic_fast_forward_white"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/exomedia_controls_next_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:minHeight="24dp"
|
||||
android:minWidth="24dp"
|
||||
app:srcCompat="@drawable/exomedia_ic_skip_next_white"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/exomedia_controls_video_loading"
|
||||
style="?android:progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"/>
|
||||
</com.devbrackets.android.exomedia.ui.widget.FitsSystemWindowRelativeLayout>
|
||||
110
app/src/main/res/layout/player_popup.xml
Normal file
@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/blackBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"/>
|
||||
|
||||
<com.devbrackets.android.exomedia.ui.widget.EMVideoView
|
||||
android:id="@+id/popupVideoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/endScreen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
tools:background="@android:color/white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:weightSum="2">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/controlAnimationView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:src="@drawable/ic_action_av_fast_rewind"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
tools:ignore="ContentDescription"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playbackControlRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:paddingBottom="5dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:gravity="bottom|center"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/popup_controls_bg"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playbackCurrentTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingRight="2dp"
|
||||
android:textColor="@android:color/white"
|
||||
tools:ignore="HardcodedText"
|
||||
android:text="-:--:--"/>
|
||||
|
||||
<!--style="?android:attr/progressBarStyleHorizontal"-->
|
||||
<SeekBar
|
||||
android:id="@+id/playbackSeekBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:max="100"
|
||||
android:layout_weight="1"
|
||||
android:progress="0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playbackEndTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingRight="2dp"
|
||||
android:textColor="@android:color/white"
|
||||
tools:ignore="HardcodedText"
|
||||
android:text="-:--:--"/>
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/loadingPanel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:background="@color/black"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
tools:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"/>
|
||||
</RelativeLayout>
|
||||
</FrameLayout>
|
||||
69
app/src/main/res/layout/player_popup_notification.xml
Normal file
@ -0,0 +1,69 @@
|
||||
<?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:id="@+id/notificationContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:background="@color/background_notification_color"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/notificationCover"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/dummy_thumbnail"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notificationSongName"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:maxLines="1"
|
||||
tools:text="title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notificationArtist"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:maxLines="1"
|
||||
tools:text="artist"/>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationPlayPause"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="5dp"
|
||||
android:background="#00ffffff"
|
||||
android:clickable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_pause_white_24dp"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/notificationStop"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="5dp"
|
||||
android:background="#00ffffff"
|
||||
android:clickable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_close_white_24dp"
|
||||
tools:ignore="ContentDescription"/>
|
||||
</LinearLayout>
|
||||
@ -22,6 +22,10 @@
|
||||
app:showAsAction="ifRoom"
|
||||
android:icon="?attr/cast"/>
|
||||
|
||||
<item android:id="@+id/menu_item_popup"
|
||||
app:showAsAction="never"
|
||||
android:title="@string/open_in_popup_mode"/>
|
||||
|
||||
<item android:id="@+id/menu_item_openInBrowser"
|
||||
app:showAsAction="never"
|
||||
android:title="@string/open_in_browser" />
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="fdroid_vlc_url" translatable="false">https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc</string>
|
||||
<string name="open_in_browser">Open in browser</string>
|
||||
<string name="open_in_popup_mode">Open in popup mode</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="loading">Loading</string>
|
||||
<string name="download">Download</string>
|
||||
@ -188,6 +189,7 @@
|
||||
<string name="msg_copied">Copied to clipboard.</string>
|
||||
<string name="no_available_dir">Please select an available download directory.</string>
|
||||
<string name="msg_restart">You have to restart the application to apply the theme.\n\nDo you want to restart now?</string>
|
||||
<string name="msg_popup_permission">This permission is needed to\nopen in popup mode</string>
|
||||
|
||||
<!-- Checksum types -->
|
||||
<string name="md5" translatable="false">MD5</string>
|
||||
|
||||