merge addExoplayer
This commit is contained in:
commit
8a29567572
20 changed files with 2322 additions and 35 deletions
|
@ -43,4 +43,5 @@ dependencies {
|
||||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||||
compile 'org.apache.directory.studio:org.apache.commons.lang:2.6'
|
compile 'org.apache.directory.studio:org.apache.commons.lang:2.6'
|
||||||
|
compile 'com.google.android.exoplayer:exoplayer:r1.5.5'
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,20 +79,39 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".PlayVideoActivity"
|
android:name=".player.PlayVideoActivity"
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
android:parentActivityName=".VideoItemDetailActivity"
|
|
||||||
android:theme="@style/VideoPlayerTheme"
|
android:theme="@style/VideoPlayerTheme"
|
||||||
tools:ignore="UnusedAttribute"></activity>
|
tools:ignore="UnusedAttribute"/>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".BackgroundPlayer"
|
android:name=".player.BackgroundPlayer"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/background_player_name"/>
|
android:label="@string/background_player_name"/>
|
||||||
|
<activity
|
||||||
|
android:name=".player.ExoPlayerActivity"
|
||||||
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:theme="@style/PlayerTheme">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.schabi.newpipe.exoplayer.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:scheme="asset" />
|
||||||
|
<data android:scheme="file" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<service
|
||||||
|
android:name=".player.BackgroundPlayer"
|
||||||
|
android:label="@string/background_player_name"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:label="@string/settings_activity_title"></activity>
|
android:label="@string/settings_activity_title" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".PanicResponderActivity"
|
android:name=".PanicResponderActivity"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
|
@ -108,7 +127,6 @@
|
||||||
android:name=".ExitActivity"
|
android:name=".ExitActivity"
|
||||||
android:label="@string/general_error"
|
android:label="@string/general_error"
|
||||||
android:theme="@android:style/Theme.NoDisplay" />
|
android:theme="@android:style/Theme.NoDisplay" />
|
||||||
<activity android:name=".ErrorActivity"></activity>
|
<activity android:name=".ErrorActivity"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -38,6 +38,7 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.util.Util;
|
||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||||
|
@ -46,6 +47,7 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.AudioStream;
|
import org.schabi.newpipe.extractor.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.ParsingException;
|
import org.schabi.newpipe.extractor.ParsingException;
|
||||||
|
@ -56,6 +58,9 @@ import org.schabi.newpipe.extractor.StreamPreviewInfo;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.VideoStream;
|
import org.schabi.newpipe.extractor.VideoStream;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||||
|
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||||
|
import org.schabi.newpipe.player.PlayVideoActivity;
|
||||||
|
import org.schabi.newpipe.player.ExoPlayerActivity;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -858,12 +863,11 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
// External Player
|
// External Player
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
try {
|
try {
|
||||||
intent.setAction(Intent.ACTION_VIEW);
|
intent.setAction(Intent.ACTION_VIEW)
|
||||||
|
.setDataAndType(Uri.parse(selectedVideoStream.url),
|
||||||
intent.setDataAndType(Uri.parse(selectedVideoStream.url),
|
MediaFormat.getMimeById(selectedVideoStream.format))
|
||||||
MediaFormat.getMimeById(selectedVideoStream.format));
|
.putExtra(Intent.EXTRA_TITLE, info.title)
|
||||||
intent.putExtra(Intent.EXTRA_TITLE, info.title);
|
.putExtra("title", info.title);
|
||||||
intent.putExtra("title", info.title);
|
|
||||||
|
|
||||||
activity.startActivity(intent); // HERE !!!
|
activity.startActivity(intent); // HERE !!!
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -873,9 +877,9 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
|
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent()
|
||||||
intent.setAction(Intent.ACTION_VIEW);
|
.setAction(Intent.ACTION_VIEW)
|
||||||
intent.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url)));
|
.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url)));
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -887,15 +891,43 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
});
|
});
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
|
.getBoolean(activity.getString(R.string.use_exoplayer_key), false)) {
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
startActivity(intent);
|
||||||
|
} else if((info.audio_streams != null && !info.audio_streams.isEmpty()) &&
|
||||||
|
(info.video_only_streams != null && !info.video_only_streams.isEmpty())) {
|
||||||
|
// try smooth streaming
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//default streaming
|
||||||
|
Intent intent = new Intent(activity, ExoPlayerActivity.class)
|
||||||
|
.setDataAndType(Uri.parse(selectedVideoStream.url),
|
||||||
|
MediaFormat.getMimeById(selectedVideoStream.format))
|
||||||
|
.putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_OTHER);
|
||||||
|
|
||||||
|
activity.startActivity(intent); // HERE !!!
|
||||||
|
}
|
||||||
|
//-------------
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Internal Player
|
// Internal Player
|
||||||
Intent intent = new Intent(activity, PlayVideoActivity.class);
|
Intent intent = new Intent(activity, PlayVideoActivity.class)
|
||||||
intent.putExtra(PlayVideoActivity.VIDEO_TITLE, info.title);
|
.putExtra(PlayVideoActivity.VIDEO_TITLE, info.title)
|
||||||
intent.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url);
|
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url)
|
||||||
intent.putExtra(PlayVideoActivity.VIDEO_URL, info.webpage_url);
|
.putExtra(PlayVideoActivity.VIDEO_URL, info.webpage_url)
|
||||||
intent.putExtra(PlayVideoActivity.START_POSITION, info.start_position);
|
.putExtra(PlayVideoActivity.START_POSITION, info.start_position);
|
||||||
activity.startActivity(intent); //also HERE !!!
|
activity.startActivity(intent); //also HERE !!!
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------
|
// --------------------------------------------
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
|
@ -20,6 +20,12 @@ import android.util.Log;
|
||||||
import android.widget.RemoteViews;
|
import android.widget.RemoteViews;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.ActivityCommunicator;
|
||||||
|
import org.schabi.newpipe.BuildConfig;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.VideoItemDetailActivity;
|
||||||
|
import org.schabi.newpipe.VideoItemDetailFragment;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -0,0 +1,565 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Christian Schabesberger on 24.12.15.
|
||||||
|
*
|
||||||
|
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||||
|
* ExoPlayerActivity.java is part of NewPipe. all changes are under GPL3
|
||||||
|
* ExoPlayerActivity.java is part of NewPipe. all changes are under GPL3
|
||||||
|
*
|
||||||
|
* NewPipe is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPipe is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.player.exoplayer.DashRendererBuilder;
|
||||||
|
import org.schabi.newpipe.player.exoplayer.EventLogger;
|
||||||
|
import org.schabi.newpipe.player.exoplayer.ExtractorRendererBuilder;
|
||||||
|
import org.schabi.newpipe.player.exoplayer.HlsRendererBuilder;
|
||||||
|
import org.schabi.newpipe.player.exoplayer.NPExoPlayer;
|
||||||
|
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
||||||
|
import org.schabi.newpipe.player.exoplayer.SmoothStreamingRendererBuilder;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.AspectRatioFrameLayout;
|
||||||
|
import com.google.android.exoplayer.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||||
|
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||||
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
|
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||||
|
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
|
||||||
|
import com.google.android.exoplayer.drm.UnsupportedDrmException;
|
||||||
|
import com.google.android.exoplayer.metadata.GeobMetadata;
|
||||||
|
import com.google.android.exoplayer.metadata.PrivMetadata;
|
||||||
|
import com.google.android.exoplayer.metadata.TxxxMetadata;
|
||||||
|
import com.google.android.exoplayer.text.CaptionStyleCompat;
|
||||||
|
import com.google.android.exoplayer.text.Cue;
|
||||||
|
import com.google.android.exoplayer.text.SubtitleLayout;
|
||||||
|
import com.google.android.exoplayer.util.DebugTextViewHelper;
|
||||||
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
import com.google.android.exoplayer.util.VerboseLogUtil;
|
||||||
|
|
||||||
|
import android.Manifest.permission;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnKeyListener;
|
||||||
|
import android.view.View.OnTouchListener;
|
||||||
|
import android.view.accessibility.CaptioningManager;
|
||||||
|
import android.widget.MediaController;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
import android.widget.PopupMenu.OnMenuItemClickListener;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.net.CookieHandler;
|
||||||
|
import java.net.CookieManager;
|
||||||
|
import java.net.CookiePolicy;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An activity that plays media using {@link NPExoPlayer}.
|
||||||
|
*/
|
||||||
|
public class ExoPlayerActivity extends Activity {
|
||||||
|
|
||||||
|
// For use within demo app code.
|
||||||
|
public static final String CONTENT_ID_EXTRA = "content_id";
|
||||||
|
public static final String CONTENT_TYPE_EXTRA = "content_type";
|
||||||
|
public static final String PROVIDER_EXTRA = "provider";
|
||||||
|
|
||||||
|
// For use when launching the demo app using adb.
|
||||||
|
private static final String CONTENT_EXT_EXTRA = "type";
|
||||||
|
|
||||||
|
private static final String TAG = "PlayerActivity";
|
||||||
|
private static final int MENU_GROUP_TRACKS = 1;
|
||||||
|
private static final int ID_OFFSET = 2;
|
||||||
|
|
||||||
|
private static final CookieManager defaultCookieManager;
|
||||||
|
static {
|
||||||
|
defaultCookieManager = new CookieManager();
|
||||||
|
defaultCookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventLogger eventLogger;
|
||||||
|
private MediaController mediaController;
|
||||||
|
private View shutterView;
|
||||||
|
private AspectRatioFrameLayout videoFrame;
|
||||||
|
private SurfaceView surfaceView;
|
||||||
|
private SubtitleLayout subtitleLayout;
|
||||||
|
|
||||||
|
private NPExoPlayer player;
|
||||||
|
private boolean playerNeedsPrepare;
|
||||||
|
|
||||||
|
private long playerPosition;
|
||||||
|
private boolean enableBackgroundAudio = true;
|
||||||
|
|
||||||
|
private Uri contentUri;
|
||||||
|
private int contentType;
|
||||||
|
private String contentId;
|
||||||
|
private String provider;
|
||||||
|
|
||||||
|
private AudioCapabilitiesReceiver audioCapabilitiesReceiver;
|
||||||
|
|
||||||
|
|
||||||
|
NPExoPlayer.Listener exoPlayerListener = new NPExoPlayer.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onStateChanged(boolean playWhenReady, int playbackState) {
|
||||||
|
if (playbackState == ExoPlayer.STATE_ENDED) {
|
||||||
|
showControls();
|
||||||
|
}
|
||||||
|
String text = "playWhenReady=" + playWhenReady + ", playbackState=";
|
||||||
|
switch(playbackState) {
|
||||||
|
case ExoPlayer.STATE_BUFFERING:
|
||||||
|
text += "buffering";
|
||||||
|
break;
|
||||||
|
case ExoPlayer.STATE_ENDED:
|
||||||
|
text += "ended";
|
||||||
|
break;
|
||||||
|
case ExoPlayer.STATE_IDLE:
|
||||||
|
text += "idle";
|
||||||
|
break;
|
||||||
|
case ExoPlayer.STATE_PREPARING:
|
||||||
|
text += "preparing";
|
||||||
|
break;
|
||||||
|
case ExoPlayer.STATE_READY:
|
||||||
|
text += "ready";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
text += "unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//todo: put text in some log
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception e) {
|
||||||
|
String errorString = null;
|
||||||
|
if (e instanceof UnsupportedDrmException) {
|
||||||
|
// Special case DRM failures.
|
||||||
|
UnsupportedDrmException unsupportedDrmException = (UnsupportedDrmException) e;
|
||||||
|
errorString = getString(Util.SDK_INT < 18 ? R.string.error_drm_not_supported
|
||||||
|
: unsupportedDrmException.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
||||||
|
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
|
||||||
|
} else if (e instanceof ExoPlaybackException
|
||||||
|
&& e.getCause() instanceof DecoderInitializationException) {
|
||||||
|
// Special case for decoder initialization failures.
|
||||||
|
DecoderInitializationException decoderInitializationException =
|
||||||
|
(DecoderInitializationException) e.getCause();
|
||||||
|
if (decoderInitializationException.decoderName == null) {
|
||||||
|
if (decoderInitializationException.getCause() instanceof DecoderQueryException) {
|
||||||
|
errorString = getString(R.string.error_querying_decoders);
|
||||||
|
} else if (decoderInitializationException.secureDecoderRequired) {
|
||||||
|
errorString = getString(R.string.error_no_secure_decoder,
|
||||||
|
decoderInitializationException.mimeType);
|
||||||
|
} else {
|
||||||
|
errorString = getString(R.string.error_no_decoder,
|
||||||
|
decoderInitializationException.mimeType);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorString = getString(R.string.error_instantiating_decoder,
|
||||||
|
decoderInitializationException.decoderName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorString != null) {
|
||||||
|
Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
playerNeedsPrepare = true;
|
||||||
|
showControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthAspectRatio) {
|
||||||
|
shutterView.setVisibility(View.GONE);
|
||||||
|
videoFrame.setAspectRatio(
|
||||||
|
height == 0 ? 1 : (width * pixelWidthAspectRatio) / height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() {
|
||||||
|
@Override
|
||||||
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
|
if (player != null) {
|
||||||
|
player.setSurface(holder.getSurface());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
|
if (player != null) {
|
||||||
|
player.blockingClearSurface();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NPExoPlayer.CaptionListener captionListener = new NPExoPlayer.CaptionListener() {
|
||||||
|
@Override
|
||||||
|
public void onCues(List<Cue> cues) {
|
||||||
|
subtitleLayout.setCues(cues);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NPExoPlayer.Id3MetadataListener id3MetadataListener = new NPExoPlayer.Id3MetadataListener() {
|
||||||
|
@Override
|
||||||
|
public void onId3Metadata(Map<String, Object> metadata) {
|
||||||
|
for (Map.Entry<String, Object> entry : metadata.entrySet()) {
|
||||||
|
if (TxxxMetadata.TYPE.equals(entry.getKey())) {
|
||||||
|
TxxxMetadata txxxMetadata = (TxxxMetadata) entry.getValue();
|
||||||
|
Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s, value=%s",
|
||||||
|
TxxxMetadata.TYPE, txxxMetadata.description, txxxMetadata.value));
|
||||||
|
} else if (PrivMetadata.TYPE.equals(entry.getKey())) {
|
||||||
|
PrivMetadata privMetadata = (PrivMetadata) entry.getValue();
|
||||||
|
Log.i(TAG, String.format("ID3 TimedMetadata %s: owner=%s",
|
||||||
|
PrivMetadata.TYPE, privMetadata.owner));
|
||||||
|
} else if (GeobMetadata.TYPE.equals(entry.getKey())) {
|
||||||
|
GeobMetadata geobMetadata = (GeobMetadata) entry.getValue();
|
||||||
|
Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s",
|
||||||
|
GeobMetadata.TYPE, geobMetadata.mimeType, geobMetadata.filename,
|
||||||
|
geobMetadata.description));
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, String.format("ID3 TimedMetadata %s", entry.getKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioCapabilitiesReceiver.Listener audioCapabilitiesListener = new AudioCapabilitiesReceiver.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) {
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean backgrounded = player.getBackgrounded();
|
||||||
|
boolean playWhenReady = player.getPlayWhenReady();
|
||||||
|
releasePlayer();
|
||||||
|
preparePlayer(playWhenReady);
|
||||||
|
player.setBackgrounded(backgrounded);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Activity lifecycle
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.exo_player_activity);
|
||||||
|
View root = findViewById(R.id.root);
|
||||||
|
root.setOnTouchListener(new OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||||
|
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
toggleControlsVisibility();
|
||||||
|
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
view.performClick();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
root.setOnKeyListener(new OnKeyListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE
|
||||||
|
|| keyCode == KeyEvent.KEYCODE_MENU) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mediaController.dispatchKeyEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
shutterView = findViewById(R.id.shutter);
|
||||||
|
|
||||||
|
videoFrame = (AspectRatioFrameLayout) findViewById(R.id.video_frame);
|
||||||
|
surfaceView = (SurfaceView) findViewById(R.id.surface_view);
|
||||||
|
surfaceView.getHolder().addCallback(surfaceHolderCallback);
|
||||||
|
subtitleLayout = (SubtitleLayout) findViewById(R.id.subtitles);
|
||||||
|
|
||||||
|
//todo: replace that creapy mediaController
|
||||||
|
mediaController = new KeyCompatibleMediaController(this);
|
||||||
|
mediaController.setAnchorView(root);
|
||||||
|
|
||||||
|
//todo: check what cookie handler does, and if we even need it
|
||||||
|
CookieHandler currentHandler = CookieHandler.getDefault();
|
||||||
|
if (currentHandler != defaultCookieManager) {
|
||||||
|
CookieHandler.setDefault(defaultCookieManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, audioCapabilitiesListener);
|
||||||
|
audioCapabilitiesReceiver.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
releasePlayer();
|
||||||
|
playerPosition = 0;
|
||||||
|
setIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
Intent intent = getIntent();
|
||||||
|
contentUri = intent.getData();
|
||||||
|
contentType = intent.getIntExtra(CONTENT_TYPE_EXTRA,
|
||||||
|
inferContentType(contentUri, intent.getStringExtra(CONTENT_EXT_EXTRA)));
|
||||||
|
contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
|
||||||
|
provider = intent.getStringExtra(PROVIDER_EXTRA);
|
||||||
|
configureSubtitleView();
|
||||||
|
if (player == null) {
|
||||||
|
if (!maybeRequestPermission()) {
|
||||||
|
preparePlayer(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
player.setBackgrounded(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
if (!enableBackgroundAudio) {
|
||||||
|
releasePlayer();
|
||||||
|
} else {
|
||||||
|
player.setBackgrounded(true);
|
||||||
|
}
|
||||||
|
shutterView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
audioCapabilitiesReceiver.unregister();
|
||||||
|
releasePlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Permission request listener method
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
||||||
|
int[] grantResults) {
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
preparePlayer(true);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.storage_permission_denied,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission management methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether it is necessary to ask for permission to read storage. If necessary, it also
|
||||||
|
* requests permission.
|
||||||
|
*
|
||||||
|
* @return true if a permission request is made. False if it is not necessary.
|
||||||
|
*/
|
||||||
|
@TargetApi(23)
|
||||||
|
private boolean maybeRequestPermission() {
|
||||||
|
if (requiresPermission(contentUri)) {
|
||||||
|
requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(23)
|
||||||
|
private boolean requiresPermission(Uri uri) {
|
||||||
|
return Util.SDK_INT >= 23
|
||||||
|
&& Util.isLocalFileUri(uri)
|
||||||
|
&& checkSelfPermission(permission.READ_EXTERNAL_STORAGE)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal methods
|
||||||
|
|
||||||
|
private RendererBuilder getRendererBuilder() {
|
||||||
|
String userAgent = Util.getUserAgent(this, "NewPipeExoPlayer");
|
||||||
|
switch (contentType) {
|
||||||
|
case Util.TYPE_SS:
|
||||||
|
// default
|
||||||
|
//return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString());
|
||||||
|
case Util.TYPE_DASH:
|
||||||
|
// if a dash manifest is available
|
||||||
|
//return new DashRendererBuilder(this, userAgent, contentUri.toString());
|
||||||
|
case Util.TYPE_HLS:
|
||||||
|
// for livestreams
|
||||||
|
return new HlsRendererBuilder(this, userAgent, contentUri.toString());
|
||||||
|
case Util.TYPE_OTHER:
|
||||||
|
// video only streaming
|
||||||
|
return new ExtractorRendererBuilder(this, userAgent, contentUri);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unsupported type: " + contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void preparePlayer(boolean playWhenReady) {
|
||||||
|
if (player == null) {
|
||||||
|
player = new NPExoPlayer(getRendererBuilder());
|
||||||
|
player.addListener(exoPlayerListener);
|
||||||
|
player.setCaptionListener(captionListener);
|
||||||
|
player.setMetadataListener(id3MetadataListener);
|
||||||
|
player.seekTo(playerPosition);
|
||||||
|
playerNeedsPrepare = true;
|
||||||
|
mediaController.setMediaPlayer(player.getPlayerControl());
|
||||||
|
mediaController.setEnabled(true);
|
||||||
|
eventLogger = new EventLogger();
|
||||||
|
eventLogger.startSession();
|
||||||
|
player.addListener(eventLogger);
|
||||||
|
player.setInfoListener(eventLogger);
|
||||||
|
player.setInternalErrorListener(eventLogger);
|
||||||
|
}
|
||||||
|
if (playerNeedsPrepare) {
|
||||||
|
player.prepare();
|
||||||
|
playerNeedsPrepare = false;
|
||||||
|
}
|
||||||
|
player.setSurface(surfaceView.getHolder().getSurface());
|
||||||
|
player.setPlayWhenReady(playWhenReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releasePlayer() {
|
||||||
|
if (player != null) {
|
||||||
|
playerPosition = player.getCurrentPosition();
|
||||||
|
player.release();
|
||||||
|
player = null;
|
||||||
|
eventLogger.endSession();
|
||||||
|
eventLogger = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleControlsVisibility() {
|
||||||
|
if (mediaController.isShowing()) {
|
||||||
|
mediaController.hide();
|
||||||
|
} else {
|
||||||
|
showControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showControls() {
|
||||||
|
mediaController.show(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureSubtitleView() {
|
||||||
|
CaptionStyleCompat style;
|
||||||
|
float fontScale;
|
||||||
|
if (Util.SDK_INT >= 19) {
|
||||||
|
style = getUserCaptionStyleV19();
|
||||||
|
fontScale = getUserCaptionFontScaleV19();
|
||||||
|
} else {
|
||||||
|
style = CaptionStyleCompat.DEFAULT;
|
||||||
|
fontScale = 1.0f;
|
||||||
|
}
|
||||||
|
subtitleLayout.setStyle(style);
|
||||||
|
subtitleLayout.setFractionalTextSize(SubtitleLayout.DEFAULT_TEXT_SIZE_FRACTION * fontScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(19)
|
||||||
|
private float getUserCaptionFontScaleV19() {
|
||||||
|
CaptioningManager captioningManager =
|
||||||
|
(CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
|
||||||
|
return captioningManager.getFontScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(19)
|
||||||
|
private CaptionStyleCompat getUserCaptionStyleV19() {
|
||||||
|
CaptioningManager captioningManager =
|
||||||
|
(CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
|
||||||
|
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a best guess to infer the type from a media {@link Uri} and an optional overriding file
|
||||||
|
* extension.
|
||||||
|
*
|
||||||
|
* @param uri The {@link Uri} of the media.
|
||||||
|
* @param fileExtension An overriding file extension.
|
||||||
|
* @return The inferred type.
|
||||||
|
*/
|
||||||
|
private static int inferContentType(Uri uri, String fileExtension) {
|
||||||
|
String lastPathSegment = !TextUtils.isEmpty(fileExtension) ? "." + fileExtension
|
||||||
|
: uri.getLastPathSegment();
|
||||||
|
return Util.inferContentType(lastPathSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class KeyCompatibleMediaController extends MediaController {
|
||||||
|
|
||||||
|
private MediaController.MediaPlayerControl playerControl;
|
||||||
|
|
||||||
|
public KeyCompatibleMediaController(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMediaPlayer(MediaController.MediaPlayerControl playerControl) {
|
||||||
|
super.setMediaPlayer(playerControl);
|
||||||
|
this.playerControl = playerControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
|
int keyCode = event.getKeyCode();
|
||||||
|
if (playerControl.canSeekForward() && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
|
||||||
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
playerControl.seekTo(playerControl.getCurrentPosition() + 15000); // milliseconds
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (playerControl.canSeekBackward() && keyCode == KeyEvent.KEYCODE_MEDIA_REWIND) {
|
||||||
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
playerControl.seekTo(playerControl.getCurrentPosition() - 5000); // milliseconds
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.dispatchKeyEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -27,6 +27,9 @@ import android.widget.MediaController;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.VideoView;
|
import android.widget.VideoView;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.App;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||||
* PlayVideoActivity.java is part of NewPipe.
|
* PlayVideoActivity.java is part of NewPipe.
|
||||||
|
@ -191,7 +194,6 @@ public class PlayVideoActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
App.checkStartTor(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -0,0 +1,268 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,599 @@
|
||||||
|
/*
|
||||||
|
* 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.CodecCounters;
|
||||||
|
import com.google.android.exoplayer.DummyTrackRenderer;
|
||||||
|
import com.google.android.exoplayer.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||||
|
import com.google.android.exoplayer.MediaCodecTrackRenderer;
|
||||||
|
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||||
|
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||||
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
|
import com.google.android.exoplayer.TimeRange;
|
||||||
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
|
import com.google.android.exoplayer.audio.AudioTrack;
|
||||||
|
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||||
|
import com.google.android.exoplayer.chunk.Format;
|
||||||
|
import com.google.android.exoplayer.dash.DashChunkSource;
|
||||||
|
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||||
|
import com.google.android.exoplayer.hls.HlsSampleSource;
|
||||||
|
import com.google.android.exoplayer.metadata.MetadataTrackRenderer.MetadataRenderer;
|
||||||
|
import com.google.android.exoplayer.text.Cue;
|
||||||
|
import com.google.android.exoplayer.text.TextRenderer;
|
||||||
|
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
||||||
|
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
||||||
|
import com.google.android.exoplayer.util.DebugTextViewHelper;
|
||||||
|
import com.google.android.exoplayer.util.PlayerControl;
|
||||||
|
|
||||||
|
import android.media.MediaCodec.CryptoException;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper around {@link ExoPlayer} that provides a higher level interface. It can be prepared
|
||||||
|
* with one of a number of {@link RendererBuilder} classes to suit different use cases (e.g. DASH,
|
||||||
|
* SmoothStreaming and so on).
|
||||||
|
*/
|
||||||
|
public class NPExoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener,
|
||||||
|
HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
|
||||||
|
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
|
||||||
|
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer,
|
||||||
|
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds renderers for the player.
|
||||||
|
*/
|
||||||
|
public interface RendererBuilder {
|
||||||
|
/**
|
||||||
|
* Builds renderers for playback.
|
||||||
|
*
|
||||||
|
* @param player The player for which renderers are being built. {@link NPExoPlayer#onRenderers}
|
||||||
|
* should be invoked once the renderers have been built. If building fails,
|
||||||
|
* {@link NPExoPlayer#onRenderersError} should be invoked.
|
||||||
|
*/
|
||||||
|
void buildRenderers(NPExoPlayer player);
|
||||||
|
/**
|
||||||
|
* Cancels the current build operation, if there is one. Else does nothing.
|
||||||
|
* <p>
|
||||||
|
* A canceled build operation must not invoke {@link NPExoPlayer#onRenderers} or
|
||||||
|
* {@link NPExoPlayer#onRenderersError} on the player, which may have been released.
|
||||||
|
*/
|
||||||
|
void cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for core events.
|
||||||
|
*/
|
||||||
|
public interface Listener {
|
||||||
|
void onStateChanged(boolean playWhenReady, int playbackState);
|
||||||
|
void onError(Exception e);
|
||||||
|
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||||
|
float pixelWidthHeightRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for internal errors.
|
||||||
|
* <p>
|
||||||
|
* These errors are not visible to the user, and hence this listener is provided for
|
||||||
|
* informational purposes only. Note however that an internal error may cause a fatal
|
||||||
|
* error if the player fails to recover. If this happens, {@link Listener#onError(Exception)}
|
||||||
|
* will be invoked.
|
||||||
|
*/
|
||||||
|
public interface InternalErrorListener {
|
||||||
|
void onRendererInitializationError(Exception e);
|
||||||
|
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
|
||||||
|
void onAudioTrackWriteError(AudioTrack.WriteException e);
|
||||||
|
void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
|
||||||
|
void onDecoderInitializationError(DecoderInitializationException e);
|
||||||
|
void onCryptoError(CryptoException e);
|
||||||
|
void onLoadError(int sourceId, IOException e);
|
||||||
|
void onDrmSessionManagerError(Exception e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for debugging information.
|
||||||
|
*/
|
||||||
|
public interface InfoListener {
|
||||||
|
void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs);
|
||||||
|
void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs);
|
||||||
|
void onDroppedFrames(int count, long elapsed);
|
||||||
|
void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate);
|
||||||
|
void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
|
||||||
|
long mediaStartTimeMs, long mediaEndTimeMs);
|
||||||
|
void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
|
||||||
|
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs);
|
||||||
|
void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
|
||||||
|
long initializationDurationMs);
|
||||||
|
void onAvailableRangeChanged(int sourceId, TimeRange availableRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for receiving notifications of timed text.
|
||||||
|
*/
|
||||||
|
public interface CaptionListener {
|
||||||
|
void onCues(List<Cue> cues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for receiving ID3 metadata parsed from the media stream.
|
||||||
|
*/
|
||||||
|
public interface Id3MetadataListener {
|
||||||
|
void onId3Metadata(Map<String, Object> metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants pulled into this class for convenience.
|
||||||
|
public static final int STATE_IDLE = ExoPlayer.STATE_IDLE;
|
||||||
|
public static final int STATE_PREPARING = ExoPlayer.STATE_PREPARING;
|
||||||
|
public static final int STATE_BUFFERING = ExoPlayer.STATE_BUFFERING;
|
||||||
|
public static final int STATE_READY = ExoPlayer.STATE_READY;
|
||||||
|
public static final int STATE_ENDED = ExoPlayer.STATE_ENDED;
|
||||||
|
public static final int TRACK_DISABLED = ExoPlayer.TRACK_DISABLED;
|
||||||
|
public static final int TRACK_DEFAULT = ExoPlayer.TRACK_DEFAULT;
|
||||||
|
|
||||||
|
public static final int RENDERER_COUNT = 4;
|
||||||
|
public static final int TYPE_VIDEO = 0;
|
||||||
|
public static final int TYPE_AUDIO = 1;
|
||||||
|
public static final int TYPE_TEXT = 2;
|
||||||
|
public static final int TYPE_METADATA = 3;
|
||||||
|
|
||||||
|
private static final int RENDERER_BUILDING_STATE_IDLE = 1;
|
||||||
|
private static final int RENDERER_BUILDING_STATE_BUILDING = 2;
|
||||||
|
private static final int RENDERER_BUILDING_STATE_BUILT = 3;
|
||||||
|
|
||||||
|
private final RendererBuilder rendererBuilder;
|
||||||
|
private final ExoPlayer player;
|
||||||
|
private final PlayerControl playerControl;
|
||||||
|
private final Handler mainHandler;
|
||||||
|
private final CopyOnWriteArrayList<Listener> listeners;
|
||||||
|
|
||||||
|
private int rendererBuildingState;
|
||||||
|
private int lastReportedPlaybackState;
|
||||||
|
private boolean lastReportedPlayWhenReady;
|
||||||
|
|
||||||
|
private Surface surface;
|
||||||
|
private TrackRenderer videoRenderer;
|
||||||
|
private CodecCounters codecCounters;
|
||||||
|
private Format videoFormat;
|
||||||
|
private int videoTrackToRestore;
|
||||||
|
|
||||||
|
private BandwidthMeter bandwidthMeter;
|
||||||
|
private boolean backgrounded;
|
||||||
|
|
||||||
|
private CaptionListener captionListener;
|
||||||
|
private Id3MetadataListener id3MetadataListener;
|
||||||
|
private InternalErrorListener internalErrorListener;
|
||||||
|
private InfoListener infoListener;
|
||||||
|
|
||||||
|
public NPExoPlayer(RendererBuilder rendererBuilder) {
|
||||||
|
this.rendererBuilder = rendererBuilder;
|
||||||
|
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000);
|
||||||
|
player.addListener(this);
|
||||||
|
playerControl = new PlayerControl(player);
|
||||||
|
mainHandler = new Handler();
|
||||||
|
listeners = new CopyOnWriteArrayList<>();
|
||||||
|
lastReportedPlaybackState = STATE_IDLE;
|
||||||
|
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
|
||||||
|
// Disable text initially.
|
||||||
|
player.setSelectedTrack(TYPE_TEXT, TRACK_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerControl getPlayerControl() {
|
||||||
|
return playerControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(Listener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeListener(Listener listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInternalErrorListener(InternalErrorListener listener) {
|
||||||
|
internalErrorListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInfoListener(InfoListener listener) {
|
||||||
|
infoListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaptionListener(CaptionListener listener) {
|
||||||
|
captionListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadataListener(Id3MetadataListener listener) {
|
||||||
|
id3MetadataListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSurface(Surface surface) {
|
||||||
|
this.surface = surface;
|
||||||
|
pushSurface(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Surface getSurface() {
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void blockingClearSurface() {
|
||||||
|
surface = null;
|
||||||
|
pushSurface(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTrackCount(int type) {
|
||||||
|
return player.getTrackCount(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaFormat getTrackFormat(int type, int index) {
|
||||||
|
return player.getTrackFormat(type, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSelectedTrack(int type) {
|
||||||
|
return player.getSelectedTrack(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedTrack(int type, int index) {
|
||||||
|
player.setSelectedTrack(type, index);
|
||||||
|
if (type == TYPE_TEXT && index < 0 && captionListener != null) {
|
||||||
|
captionListener.onCues(Collections.<Cue>emptyList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBackgrounded() {
|
||||||
|
return backgrounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgrounded(boolean backgrounded) {
|
||||||
|
if (this.backgrounded == backgrounded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.backgrounded = backgrounded;
|
||||||
|
if (backgrounded) {
|
||||||
|
videoTrackToRestore = getSelectedTrack(TYPE_VIDEO);
|
||||||
|
setSelectedTrack(TYPE_VIDEO, TRACK_DISABLED);
|
||||||
|
blockingClearSurface();
|
||||||
|
} else {
|
||||||
|
setSelectedTrack(TYPE_VIDEO, videoTrackToRestore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void prepare() {
|
||||||
|
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT) {
|
||||||
|
player.stop();
|
||||||
|
}
|
||||||
|
rendererBuilder.cancel();
|
||||||
|
videoFormat = null;
|
||||||
|
videoRenderer = null;
|
||||||
|
rendererBuildingState = RENDERER_BUILDING_STATE_BUILDING;
|
||||||
|
maybeReportPlayerState();
|
||||||
|
rendererBuilder.buildRenderers(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked with the results from a {@link RendererBuilder}.
|
||||||
|
*
|
||||||
|
* @param renderers Renderers indexed by {@link NPExoPlayer} TYPE_* constants. An individual
|
||||||
|
* element may be null if there do not exist tracks of the corresponding type.
|
||||||
|
* @param bandwidthMeter Provides an estimate of the currently available bandwidth. May be null.
|
||||||
|
*/
|
||||||
|
/* package */ void onRenderers(TrackRenderer[] renderers, BandwidthMeter bandwidthMeter) {
|
||||||
|
for (int i = 0; i < RENDERER_COUNT; i++) {
|
||||||
|
if (renderers[i] == null) {
|
||||||
|
// Convert a null renderer to a dummy renderer.
|
||||||
|
renderers[i] = new DummyTrackRenderer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Complete preparation.
|
||||||
|
this.videoRenderer = renderers[TYPE_VIDEO];
|
||||||
|
this.codecCounters = videoRenderer instanceof MediaCodecTrackRenderer
|
||||||
|
? ((MediaCodecTrackRenderer) videoRenderer).codecCounters
|
||||||
|
: renderers[TYPE_AUDIO] instanceof MediaCodecTrackRenderer
|
||||||
|
? ((MediaCodecTrackRenderer) renderers[TYPE_AUDIO]).codecCounters : null;
|
||||||
|
this.bandwidthMeter = bandwidthMeter;
|
||||||
|
pushSurface(false);
|
||||||
|
player.prepare(renderers);
|
||||||
|
rendererBuildingState = RENDERER_BUILDING_STATE_BUILT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked if a {@link RendererBuilder} encounters an error.
|
||||||
|
*
|
||||||
|
* @param e Describes the error.
|
||||||
|
*/
|
||||||
|
/* package */ void onRenderersError(Exception e) {
|
||||||
|
if (internalErrorListener != null) {
|
||||||
|
internalErrorListener.onRendererInitializationError(e);
|
||||||
|
}
|
||||||
|
for (Listener listener : listeners) {
|
||||||
|
listener.onError(e);
|
||||||
|
}
|
||||||
|
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
|
||||||
|
maybeReportPlayerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlayWhenReady(boolean playWhenReady) {
|
||||||
|
player.setPlayWhenReady(playWhenReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seekTo(long positionMs) {
|
||||||
|
player.seekTo(positionMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
rendererBuilder.cancel();
|
||||||
|
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
|
||||||
|
surface = null;
|
||||||
|
player.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlaybackState() {
|
||||||
|
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) {
|
||||||
|
return STATE_PREPARING;
|
||||||
|
}
|
||||||
|
int playerState = player.getPlaybackState();
|
||||||
|
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT && playerState == STATE_IDLE) {
|
||||||
|
// This is an edge case where the renderers are built, but are still being passed to the
|
||||||
|
// player's playback thread.
|
||||||
|
return STATE_PREPARING;
|
||||||
|
}
|
||||||
|
return playerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Format getFormat() {
|
||||||
|
return videoFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BandwidthMeter getBandwidthMeter() {
|
||||||
|
return bandwidthMeter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CodecCounters getCodecCounters() {
|
||||||
|
return codecCounters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return player.getCurrentPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDuration() {
|
||||||
|
return player.getDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBufferedPercentage() {
|
||||||
|
return player.getBufferedPercentage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getPlayWhenReady() {
|
||||||
|
return player.getPlayWhenReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ Looper getPlaybackLooper() {
|
||||||
|
return player.getPlaybackLooper();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ Handler getMainHandler() {
|
||||||
|
return mainHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerStateChanged(boolean playWhenReady, int state) {
|
||||||
|
maybeReportPlayerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerError(ExoPlaybackException exception) {
|
||||||
|
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
|
||||||
|
for (Listener listener : listeners) {
|
||||||
|
listener.onError(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||||
|
float pixelWidthHeightRatio) {
|
||||||
|
for (Listener listener : listeners) {
|
||||||
|
listener.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDroppedFrames(int count, long elapsed) {
|
||||||
|
if (infoListener != null) {
|
||||||
|
infoListener.onDroppedFrames(count, elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) {
|
||||||
|
if (infoListener != null) {
|
||||||
|
infoListener.onBandwidthSample(elapsedMs, bytes, bitrateEstimate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDownstreamFormatChanged(int sourceId, Format format, int trigger,
|
||||||
|
long mediaTimeMs) {
|
||||||
|
if (infoListener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sourceId == TYPE_VIDEO) {
|
||||||
|
videoFormat = format;
|
||||||
|
infoListener.onVideoFormatEnabled(format, trigger, mediaTimeMs);
|
||||||
|
} else if (sourceId == TYPE_AUDIO) {
|
||||||
|
infoListener.onAudioFormatEnabled(format, trigger, mediaTimeMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrmKeysLoaded() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrmSessionManagerError(Exception e) {
|
||||||
|
if (internalErrorListener != null) {
|
||||||
|
internalErrorListener.onDrmSessionManagerError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDecoderInitializationError(DecoderInitializationException e) {
|
||||||
|
if (internalErrorListener != null) {
|
||||||
|
internalErrorListener.onDecoderInitializationError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
|
||||||
|
if (internalErrorListener != null) {
|
||||||
|
internalErrorListener.onAudioTrackInitializationError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioTrackWriteError(AudioTrack.WriteException e) {
|
||||||
|
if (internalErrorListener != null) {
|
||||||
|
internalErrorListener.onAudioTrackWriteError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
|
if (internalErrorListener != null) {
|
||||||
|
internalErrorListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCryptoError(CryptoException e) {
|
||||||
|
if (internalErrorListener != null) {
|
||||||
|
internalErrorListener.onCryptoError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
|
||||||
|
long initializationDurationMs) {
|
||||||
|
if (infoListener != null) {
|
||||||
|
infoListener.onDecoderInitialized(decoderName, elapsedRealtimeMs, initializationDurationMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadError(int sourceId, IOException e) {
|
||||||
|
if (internalErrorListener != null) {
|
||||||
|
internalErrorListener.onLoadError(sourceId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCues(List<Cue> cues) {
|
||||||
|
if (captionListener != null && getSelectedTrack(TYPE_TEXT) != TRACK_DISABLED) {
|
||||||
|
captionListener.onCues(cues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMetadata(Map<String, Object> metadata) {
|
||||||
|
if (id3MetadataListener != null && getSelectedTrack(TYPE_METADATA) != TRACK_DISABLED) {
|
||||||
|
id3MetadataListener.onId3Metadata(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) {
|
||||||
|
if (infoListener != null) {
|
||||||
|
infoListener.onAvailableRangeChanged(sourceId, availableRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayWhenReadyCommitted() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawnToSurface(Surface surface) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
|
||||||
|
long mediaStartTimeMs, long mediaEndTimeMs) {
|
||||||
|
if (infoListener != null) {
|
||||||
|
infoListener.onLoadStarted(sourceId, length, type, trigger, format, 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 (infoListener != null) {
|
||||||
|
infoListener.onLoadCompleted(sourceId, bytesLoaded, type, trigger, format, mediaStartTimeMs,
|
||||||
|
mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadCanceled(int sourceId, long bytesLoaded) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpstreamDiscarded(int sourceId, long mediaStartTimeMs, long mediaEndTimeMs) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeReportPlayerState() {
|
||||||
|
boolean playWhenReady = player.getPlayWhenReady();
|
||||||
|
int playbackState = getPlaybackState();
|
||||||
|
if (lastReportedPlayWhenReady != playWhenReady || lastReportedPlaybackState != playbackState) {
|
||||||
|
for (Listener listener : listeners) {
|
||||||
|
listener.onStateChanged(playWhenReady, playbackState);
|
||||||
|
}
|
||||||
|
lastReportedPlayWhenReady = playWhenReady;
|
||||||
|
lastReportedPlaybackState = playbackState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushSurface(boolean blockForSurfacePush) {
|
||||||
|
if (videoRenderer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockForSurfacePush) {
|
||||||
|
player.blockingSendMessage(
|
||||||
|
videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
|
||||||
|
} else {
|
||||||
|
player.sendMessage(
|
||||||
|
videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context="org.schabi.newpipe.PlayVideoActivity"
|
tools:context=".player.PlayVideoActivity"
|
||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
<VideoView android:id="@+id/video_view"
|
<VideoView android:id="@+id/video_view"
|
||||||
|
|
44
app/src/main/res/layout/exo_player_activity.xml
Normal file
44
app/src/main/res/layout/exo_player_activity.xml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?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>
|
22
app/src/main/res/values/constants.xml
Normal file
22
app/src/main/res/values/constants.xml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<!-- The minimum subtitle font size. -->
|
||||||
|
<dimen name="subtitle_minimum_font_size">13sp</dimen>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Video Item Search View Dimensions-->
|
<!-- Video Item Search View Dimensions-->
|
||||||
<!-- Text Size -->
|
<!-- Text Size -->
|
||||||
<dimen name="video_item_search_title_text_size">14sp</dimen>
|
<dimen name="video_item_search_title_text_size">14sp</dimen>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<string name="use_external_video_player_key">use_external_video_player</string>
|
<string name="use_external_video_player_key">use_external_video_player</string>
|
||||||
<string name="use_external_audio_player_key">use_external_audio_player</string>
|
<string name="use_external_audio_player_key">use_external_audio_player</string>
|
||||||
<string name="autoplay_through_intent_key">autoplay_through_intent</string>
|
<string name="autoplay_through_intent_key">autoplay_through_intent</string>
|
||||||
|
<string name="use_exoplayer_key">use_exoplayer</string>
|
||||||
|
|
||||||
<string name="default_resolution_key">default_resolution_preference</string>
|
<string name="default_resolution_key">default_resolution_preference</string>
|
||||||
<string name="default_resolution_value">360p</string>
|
<string name="default_resolution_value">360p</string>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<resources>
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="app_name" translatable="false">NewPipe</string>
|
<string name="app_name" translatable="false">NewPipe</string>
|
||||||
<string name="background_player_name">NewPipe Background Player</string>
|
<string name="background_player_name">NewPipe Background Player</string>
|
||||||
<string name="title_videoitem_detail" translatable="false">NewPipe</string>
|
<string name="title_videoitem_detail" translatable="false">NewPipe</string>
|
||||||
|
@ -116,4 +116,24 @@
|
||||||
|
|
||||||
<string name="err_dir_create">Cannot create download directory \'%1$s\'</string>
|
<string name="err_dir_create">Cannot create download directory \'%1$s\'</string>
|
||||||
<string name="info_dir_created">Created download directory \'%1$s\'</string>
|
<string name="info_dir_created">Created download directory \'%1$s\'</string>
|
||||||
|
|
||||||
|
<string name="enable_background_audio">Play in background</string>
|
||||||
|
<string name="video">Video</string>
|
||||||
|
<string name="audio">Audio</string>
|
||||||
|
<string name="text">Text</string>
|
||||||
|
<string name="logging">Logging</string>
|
||||||
|
<string name="logging_normal">Normal</string>
|
||||||
|
<string name="logging_verbose">Verbose</string>
|
||||||
|
<string name="retry">Retry</string>
|
||||||
|
<string name="off">[off]</string>
|
||||||
|
<string name="error_drm_not_supported">Protected content not supported on API levels below 18</string>
|
||||||
|
<string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
|
||||||
|
<string name="error_drm_unknown">An unknown DRM error occurred</string>
|
||||||
|
<string name="error_no_decoder">This device does not provide a decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
|
||||||
|
<string name="error_no_secure_decoder">This device does not provide a secure decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
|
||||||
|
<string name="error_querying_decoders">Unable to query device decoders</string>
|
||||||
|
<string name="error_instantiating_decoder">Unable to instantiate decoder <xliff:g id="decoder_name">%1$s</xliff:g></string>
|
||||||
|
<string name="storage_permission_denied">Permission to access storage was denied</string>
|
||||||
|
<string name="use_exoplayer_title">Use ExoPlayer</string>
|
||||||
|
<string name="use_exoplayer_summary">Experimental</string>
|
||||||
</resources>
|
</resources>
|
|
@ -1,4 +1,19 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
|
<style name="RootTheme" parent="android:Theme.Holo">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="PlayerTheme" parent="@style/RootTheme">
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:windowBackground">@android:color/black</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ExoPlayerButton">
|
||||||
|
<item name="android:layout_width">wrap_content</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:minWidth">40dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light">
|
<style name="AppTheme" parent="Theme.AppCompat.Light">
|
||||||
<item name="android:actionBarStyle">@style/NewPipeActionbarTheme</item>
|
<item name="android:actionBarStyle">@style/NewPipeActionbarTheme</item>
|
||||||
|
@ -32,4 +47,5 @@
|
||||||
<item name="android:background">@color/video_overlay_color</item>
|
<item name="android:background">@color/video_overlay_color</item>
|
||||||
<item name="background">@color/video_overlay_color</item>
|
<item name="background">@color/video_overlay_color</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -33,6 +33,11 @@
|
||||||
android:entryValues="@array/audio_format_list"
|
android:entryValues="@array/audio_format_list"
|
||||||
android:defaultValue="@string/default_audio_format_value"/>
|
android:defaultValue="@string/default_audio_format_value"/>
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="@string/use_exoplayer_key"
|
||||||
|
android:title="@string/use_exoplayer_title"
|
||||||
|
android:summary="@string/use_exoplayer_summary"
|
||||||
|
android:defaultValue="false"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="@string/settings_category_appearance"
|
android:key="@string/settings_category_appearance"
|
||||||
|
|
Loading…
Reference in a new issue