merge addExoplayer

This commit is contained in:
Christian Schabesberger 2016-03-05 16:54:24 +01:00
commit 8a29567572
20 changed files with 2322 additions and 35 deletions

View File

@ -43,4 +43,5 @@ dependencies {
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'com.github.nirhart:parallaxscroll:1.0'
compile 'org.apache.directory.studio:org.apache.commons.lang:2.6'
compile 'com.google.android.exoplayer:exoplayer:r1.5.5'
}

View File

@ -79,20 +79,39 @@
</intent-filter>
</activity>
<activity
android:name=".PlayVideoActivity"
android:name=".player.PlayVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName=".VideoItemDetailActivity"
android:theme="@style/VideoPlayerTheme"
tools:ignore="UnusedAttribute"></activity>
tools:ignore="UnusedAttribute"/>
<service
android:name=".BackgroundPlayer"
android:name=".player.BackgroundPlayer"
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
android:name=".SettingsActivity"
android:label="@string/settings_activity_title"></activity>
android:label="@string/settings_activity_title" />
<activity
android:name=".PanicResponderActivity"
android:launchMode="singleInstance"
@ -108,7 +127,6 @@
android:name=".ExitActivity"
android:label="@string/general_error"
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name=".ErrorActivity"></activity>
<activity android:name=".ErrorActivity"/>
</application>
</manifest>

View File

@ -38,6 +38,7 @@ import android.widget.Toast;
import java.io.IOException;
import com.google.android.exoplayer.util.Util;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
@ -46,6 +47,7 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import java.util.ArrayList;
import java.util.Vector;
import org.schabi.newpipe.extractor.AudioStream;
import org.schabi.newpipe.extractor.MediaFormat;
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.VideoStream;
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
Intent intent = new Intent();
try {
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(selectedVideoStream.url),
MediaFormat.getMimeById(selectedVideoStream.format));
intent.putExtra(Intent.EXTRA_TITLE, info.title);
intent.putExtra("title", info.title);
intent.setAction(Intent.ACTION_VIEW)
.setDataAndType(Uri.parse(selectedVideoStream.url),
MediaFormat.getMimeById(selectedVideoStream.format))
.putExtra(Intent.EXTRA_TITLE, info.title)
.putExtra("title", info.title);
activity.startActivity(intent); // HERE !!!
} catch (Exception e) {
@ -873,9 +877,9 @@ public class VideoItemDetailFragment extends Fragment {
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url)));
Intent intent = new Intent()
.setAction(Intent.ACTION_VIEW)
.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url)));
activity.startActivity(intent);
}
})
@ -888,13 +892,41 @@ public class VideoItemDetailFragment extends Fragment {
builder.create().show();
}
} else {
// Internal Player
Intent intent = new Intent(activity, PlayVideoActivity.class);
intent.putExtra(PlayVideoActivity.VIDEO_TITLE, info.title);
intent.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url);
intent.putExtra(PlayVideoActivity.VIDEO_URL, info.webpage_url);
intent.putExtra(PlayVideoActivity.START_POSITION, info.start_position);
activity.startActivity(intent); //also HERE !!!
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 {
// Internal Player
Intent intent = new Intent(activity, PlayVideoActivity.class)
.putExtra(PlayVideoActivity.VIDEO_TITLE, info.title)
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url)
.putExtra(PlayVideoActivity.VIDEO_URL, info.webpage_url)
.putExtra(PlayVideoActivity.START_POSITION, info.start_position);
activity.startActivity(intent); //also HERE !!!
}
}
// --------------------------------------------

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe;
package org.schabi.newpipe.player;
import android.app.Notification;
import android.app.NotificationManager;
@ -20,6 +20,12 @@ import android.util.Log;
import android.widget.RemoteViews;
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;
/**

View File

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

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe;
package org.schabi.newpipe.player;
import android.content.Context;
import android.content.Intent;
@ -27,6 +27,9 @@ import android.widget.MediaController;
import android.widget.ProgressBar;
import android.widget.VideoView;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
/**
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* PlayVideoActivity.java is part of NewPipe.
@ -191,7 +194,6 @@ public class PlayVideoActivity extends AppCompatActivity {
@Override
public void onResume() {
super.onResume();
App.checkStartTor(this);
}
@Override

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.schabi.newpipe.PlayVideoActivity"
tools:context=".player.PlayVideoActivity"
android:gravity="center">
<VideoView android:id="@+id/video_view"

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

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

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Video Item Search View Dimensions-->
<!-- Text Size -->
<dimen name="video_item_search_title_text_size">14sp</dimen>
@ -39,4 +38,4 @@
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
</resources>

View File

@ -11,6 +11,7 @@
<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="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_value">360p</string>

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<?xml version='1.0' encoding='utf-8'?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" translatable="false">NewPipe</string>
<string name="background_player_name">NewPipe Background Player</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="info_dir_created">Created download directory \'%1$s\'</string>
</resources>
<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>

View File

@ -1,4 +1,19 @@
<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. -->
<style name="AppTheme" parent="Theme.AppCompat.Light">
<item name="android:actionBarStyle">@style/NewPipeActionbarTheme</item>
@ -32,4 +47,5 @@
<item name="android:background">@color/video_overlay_color</item>
<item name="background">@color/video_overlay_color</item>
</style>
</resources>
</resources>

View File

@ -33,6 +33,11 @@
android:entryValues="@array/audio_format_list"
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
android:key="@string/settings_category_appearance"