mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	initial changes
This commit is contained in:
		| @@ -76,14 +76,14 @@ | |||||||
|                 <data android:scheme="vnd.youtube.launch" /> |                 <data android:scheme="vnd.youtube.launch" /> | ||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </activity> |         </activity> | ||||||
|         <activity android:name=".PlayVideoActivity" |         <activity android:name=".player.PlayVideoActivity" | ||||||
|             android:configChanges="orientation|keyboardHidden|screenSize" |             android:configChanges="orientation|keyboardHidden|screenSize" | ||||||
|             android:theme="@style/VideoPlayerTheme" |             android:theme="@style/VideoPlayerTheme" | ||||||
|             android:parentActivityName=".VideoItemDetailActivity" |             android:parentActivityName=".VideoItemDetailActivity" | ||||||
|             tools:ignore="UnusedAttribute"> |             tools:ignore="UnusedAttribute"> | ||||||
|         </activity> |         </activity> | ||||||
| 		<activity | 		<activity | ||||||
|             android:name=".exoplayer.ExoPlayerActivity" |             android:name=".player.ExoPlayerActivity" | ||||||
|             android:configChanges="keyboard|keyboardHidden|orientation|screenSize" |             android:configChanges="keyboard|keyboardHidden|orientation|screenSize" | ||||||
|             android:label="@string/app_name" |             android:label="@string/app_name" | ||||||
|             android:launchMode="singleInstance" |             android:launchMode="singleInstance" | ||||||
| @@ -101,7 +101,7 @@ | |||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </activity> |         </activity> | ||||||
|         <service |         <service | ||||||
|             android:name=".BackgroundPlayer" |             android:name=".player.BackgroundPlayer" | ||||||
|             android:label="@string/background_player_name" |             android:label="@string/background_player_name" | ||||||
|             android:exported="false" /> |             android:exported="false" /> | ||||||
|         <activity |         <activity | ||||||
|   | |||||||
| @@ -56,7 +56,9 @@ import org.schabi.newpipe.extractor.VideoPreviewInfo; | |||||||
| import org.schabi.newpipe.extractor.StreamingService; | import org.schabi.newpipe.extractor.StreamingService; | ||||||
| import org.schabi.newpipe.extractor.VideoInfo; | import org.schabi.newpipe.extractor.VideoInfo; | ||||||
| import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; | import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; | ||||||
| import org.schabi.newpipe.exoplayer.ExoPlayerActivity; | import org.schabi.newpipe.player.BackgroundPlayer; | ||||||
|  | import org.schabi.newpipe.player.PlayVideoActivity; | ||||||
|  | import org.schabi.newpipe.player.ExoPlayerActivity; | ||||||
|  |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -831,10 +833,17 @@ public class VideoItemDetailFragment extends Fragment { | |||||||
|         } else { |         } else { | ||||||
|             if (PreferenceManager.getDefaultSharedPreferences(activity) |             if (PreferenceManager.getDefaultSharedPreferences(activity) | ||||||
|                     .getBoolean(activity.getString(R.string.use_exoplayer_key), false)) { |                     .getBoolean(activity.getString(R.string.use_exoplayer_key), false)) { | ||||||
|  |  | ||||||
|  |                 // exo player | ||||||
|  |  | ||||||
|  |                 if(info.dashMpdUrl != null && !info.dashMpdUrl.isEmpty()) { | ||||||
|                     Intent mpdIntent = new Intent(activity, ExoPlayerActivity.class) |                     Intent mpdIntent = new Intent(activity, ExoPlayerActivity.class) | ||||||
|                             .setData(Uri.parse(info.dashMpdUrl)) |                             .setData(Uri.parse(info.dashMpdUrl)) | ||||||
|                             .putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_DASH); |                             .putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_DASH); | ||||||
|                     startActivity(mpdIntent); |                     startActivity(mpdIntent); | ||||||
|  |                 } | ||||||
|  |                 //------------- | ||||||
|  |  | ||||||
|             } else { |             } else { | ||||||
|                 // Internal Player |                 // Internal Player | ||||||
|                 Intent intent = new Intent(activity, PlayVideoActivity.class); |                 Intent intent = new Intent(activity, PlayVideoActivity.class); | ||||||
|   | |||||||
| @@ -1,729 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright (C) 2014 The Android Open Source Project |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
| package org.schabi.newpipe.exoplayer; |  | ||||||
|  |  | ||||||
| import org.schabi.newpipe.R; |  | ||||||
| import org.schabi.newpipe.exoplayer.NPExoPlayer.RendererBuilder; |  | ||||||
|  |  | ||||||
| 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.OnClickListener; |  | ||||||
| import android.view.View.OnKeyListener; |  | ||||||
| import android.view.View.OnTouchListener; |  | ||||||
| import android.view.accessibility.CaptioningManager; |  | ||||||
| import android.widget.Button; |  | ||||||
| import android.widget.MediaController; |  | ||||||
| import android.widget.PopupMenu; |  | ||||||
| import android.widget.PopupMenu.OnMenuItemClickListener; |  | ||||||
| import android.widget.TextView; |  | ||||||
| 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 implements SurfaceHolder.Callback, OnClickListener, |  | ||||||
|     NPExoPlayer.Listener, NPExoPlayer.CaptionListener, NPExoPlayer.Id3MetadataListener, |  | ||||||
|     AudioCapabilitiesReceiver.Listener { |  | ||||||
|  |  | ||||||
|   // 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 debugRootView; |  | ||||||
|   private View shutterView; |  | ||||||
|   private AspectRatioFrameLayout videoFrame; |  | ||||||
|   private SurfaceView surfaceView; |  | ||||||
|   private TextView debugTextView; |  | ||||||
|   private TextView playerStateTextView; |  | ||||||
|   private SubtitleLayout subtitleLayout; |  | ||||||
|   private Button videoButton; |  | ||||||
|   private Button audioButton; |  | ||||||
|   private Button textButton; |  | ||||||
|   private Button retryButton; |  | ||||||
|  |  | ||||||
|   private NPExoPlayer player; |  | ||||||
|   private DebugTextViewHelper debugViewHelper; |  | ||||||
|   private boolean playerNeedsPrepare; |  | ||||||
|  |  | ||||||
|   private long playerPosition; |  | ||||||
|   private boolean enableBackgroundAudio; |  | ||||||
|  |  | ||||||
|   private Uri contentUri; |  | ||||||
|   private int contentType; |  | ||||||
|   private String contentId; |  | ||||||
|   private String provider; |  | ||||||
|  |  | ||||||
|   private AudioCapabilitiesReceiver audioCapabilitiesReceiver; |  | ||||||
|  |  | ||||||
|   // 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); |  | ||||||
|     debugRootView = findViewById(R.id.controls_root); |  | ||||||
|  |  | ||||||
|     videoFrame = (AspectRatioFrameLayout) findViewById(R.id.video_frame); |  | ||||||
|     surfaceView = (SurfaceView) findViewById(R.id.surface_view); |  | ||||||
|     surfaceView.getHolder().addCallback(this); |  | ||||||
|     debugTextView = (TextView) findViewById(R.id.debug_text_view); |  | ||||||
|  |  | ||||||
|     playerStateTextView = (TextView) findViewById(R.id.player_state_view); |  | ||||||
|     subtitleLayout = (SubtitleLayout) findViewById(R.id.subtitles); |  | ||||||
|  |  | ||||||
|     mediaController = new KeyCompatibleMediaController(this); |  | ||||||
|     mediaController.setAnchorView(root); |  | ||||||
|     retryButton = (Button) findViewById(R.id.retry_button); |  | ||||||
|     retryButton.setOnClickListener(this); |  | ||||||
|     videoButton = (Button) findViewById(R.id.video_controls); |  | ||||||
|     audioButton = (Button) findViewById(R.id.audio_controls); |  | ||||||
|     textButton = (Button) findViewById(R.id.text_controls); |  | ||||||
|  |  | ||||||
|     CookieHandler currentHandler = CookieHandler.getDefault(); |  | ||||||
|     if (currentHandler != defaultCookieManager) { |  | ||||||
|       CookieHandler.setDefault(defaultCookieManager); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, this); |  | ||||||
|     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(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // OnClickListener methods |  | ||||||
|  |  | ||||||
|   @Override |  | ||||||
|   public void onClick(View view) { |  | ||||||
|     if (view == retryButton) { |  | ||||||
|       preparePlayer(true); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // AudioCapabilitiesReceiver.Listener methods |  | ||||||
|  |  | ||||||
|   @Override |  | ||||||
|   public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) { |  | ||||||
|     if (player == null) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     boolean backgrounded = player.getBackgrounded(); |  | ||||||
|     boolean playWhenReady = player.getPlayWhenReady(); |  | ||||||
|     releasePlayer(); |  | ||||||
|     preparePlayer(playWhenReady); |  | ||||||
|     player.setBackgrounded(backgrounded); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // 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: |  | ||||||
|         return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(), |  | ||||||
|             new SmoothStreamingTestMediaDrmCallback()); |  | ||||||
|       case Util.TYPE_DASH: |  | ||||||
|         return new DashRendererBuilder(this, userAgent, contentUri.toString(), |  | ||||||
|             new WidevineTestMediaDrmCallback(contentId, provider)); |  | ||||||
|       case Util.TYPE_HLS: |  | ||||||
|         return new HlsRendererBuilder(this, userAgent, contentUri.toString()); |  | ||||||
|       case Util.TYPE_OTHER: |  | ||||||
|         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(this); |  | ||||||
|       player.setCaptionListener(this); |  | ||||||
|       player.setMetadataListener(this); |  | ||||||
|       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); |  | ||||||
|       debugViewHelper = new DebugTextViewHelper(player, debugTextView); |  | ||||||
|       playerStateTextView.setVisibility(View.GONE); |  | ||||||
|       debugTextView.setVisibility(View.GONE); |  | ||||||
|       debugViewHelper.start(); |  | ||||||
|     } |  | ||||||
|     if (playerNeedsPrepare) { |  | ||||||
|       player.prepare(); |  | ||||||
|       playerNeedsPrepare = false; |  | ||||||
|       updateButtonVisibilities(); |  | ||||||
|     } |  | ||||||
|     player.setSurface(surfaceView.getHolder().getSurface()); |  | ||||||
|     player.setPlayWhenReady(playWhenReady); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void releasePlayer() { |  | ||||||
|     if (player != null) { |  | ||||||
|       debugViewHelper.stop(); |  | ||||||
|       debugViewHelper = null; |  | ||||||
|       playerPosition = player.getCurrentPosition(); |  | ||||||
|       player.release(); |  | ||||||
|       player = null; |  | ||||||
|       eventLogger.endSession(); |  | ||||||
|       eventLogger = null; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // NPExoPlayer.Listener implementation |  | ||||||
|  |  | ||||||
|   @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; |  | ||||||
|     } |  | ||||||
|     playerStateTextView.setText(text); |  | ||||||
|     updateButtonVisibilities(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @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; |  | ||||||
|     updateButtonVisibilities(); |  | ||||||
|     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); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // User controls |  | ||||||
|  |  | ||||||
|   private void updateButtonVisibilities() { |  | ||||||
|     retryButton.setVisibility(playerNeedsPrepare ? View.VISIBLE : View.GONE); |  | ||||||
|     videoButton.setVisibility(haveTracks(NPExoPlayer.TYPE_VIDEO) ? View.VISIBLE : View.GONE); |  | ||||||
|     audioButton.setVisibility(haveTracks(NPExoPlayer.TYPE_AUDIO) ? View.VISIBLE : View.GONE); |  | ||||||
|     textButton.setVisibility(haveTracks(NPExoPlayer.TYPE_TEXT) ? View.VISIBLE : View.GONE); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private boolean haveTracks(int type) { |  | ||||||
|     return player != null && player.getTrackCount(type) > 0; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public void showVideoPopup(View v) { |  | ||||||
|     PopupMenu popup = new PopupMenu(this, v); |  | ||||||
|     configurePopupWithTracks(popup, null, NPExoPlayer.TYPE_VIDEO); |  | ||||||
|     popup.show(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public void showAudioPopup(View v) { |  | ||||||
|     PopupMenu popup = new PopupMenu(this, v); |  | ||||||
|     Menu menu = popup.getMenu(); |  | ||||||
|     menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.enable_background_audio); |  | ||||||
|     final MenuItem backgroundAudioItem = menu.findItem(0); |  | ||||||
|     backgroundAudioItem.setCheckable(true); |  | ||||||
|     backgroundAudioItem.setChecked(enableBackgroundAudio); |  | ||||||
|     OnMenuItemClickListener clickListener = new OnMenuItemClickListener() { |  | ||||||
|       @Override |  | ||||||
|       public boolean onMenuItemClick(MenuItem item) { |  | ||||||
|         if (item == backgroundAudioItem) { |  | ||||||
|           enableBackgroundAudio = !item.isChecked(); |  | ||||||
|           return true; |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
|     configurePopupWithTracks(popup, clickListener, NPExoPlayer.TYPE_AUDIO); |  | ||||||
|     popup.show(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public void showTextPopup(View v) { |  | ||||||
|     PopupMenu popup = new PopupMenu(this, v); |  | ||||||
|     configurePopupWithTracks(popup, null, NPExoPlayer.TYPE_TEXT); |  | ||||||
|     popup.show(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public void showVerboseLogPopup(View v) { |  | ||||||
|     PopupMenu popup = new PopupMenu(this, v); |  | ||||||
|     Menu menu = popup.getMenu(); |  | ||||||
|     menu.add(Menu.NONE, 0, Menu.NONE, R.string.logging_normal); |  | ||||||
|     menu.add(Menu.NONE, 1, Menu.NONE, R.string.logging_verbose); |  | ||||||
|     menu.setGroupCheckable(Menu.NONE, true, true); |  | ||||||
|     menu.findItem((VerboseLogUtil.areAllTagsEnabled()) ? 1 : 0).setChecked(true); |  | ||||||
|     popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { |  | ||||||
|       @Override |  | ||||||
|       public boolean onMenuItemClick(MenuItem item) { |  | ||||||
|         if (item.getItemId() == 0) { |  | ||||||
|           VerboseLogUtil.setEnableAllTags(false); |  | ||||||
|         } else { |  | ||||||
|           VerboseLogUtil.setEnableAllTags(true); |  | ||||||
|         } |  | ||||||
|         return true; |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|     popup.show(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void configurePopupWithTracks(PopupMenu popup, |  | ||||||
|       final OnMenuItemClickListener customActionClickListener, |  | ||||||
|       final int trackType) { |  | ||||||
|     if (player == null) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     int trackCount = player.getTrackCount(trackType); |  | ||||||
|     if (trackCount == 0) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { |  | ||||||
|       @Override |  | ||||||
|       public boolean onMenuItemClick(MenuItem item) { |  | ||||||
|         return (customActionClickListener != null |  | ||||||
|             && customActionClickListener.onMenuItemClick(item)) |  | ||||||
|             || onTrackItemClick(item, trackType); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|     Menu menu = popup.getMenu(); |  | ||||||
|     // ID_OFFSET ensures we avoid clashing with Menu.NONE (which equals 0). |  | ||||||
|     menu.add(MENU_GROUP_TRACKS, NPExoPlayer.TRACK_DISABLED + ID_OFFSET, Menu.NONE, R.string.off); |  | ||||||
|     for (int i = 0; i < trackCount; i++) { |  | ||||||
|       menu.add(MENU_GROUP_TRACKS, i + ID_OFFSET, Menu.NONE, |  | ||||||
|           buildTrackName(player.getTrackFormat(trackType, i))); |  | ||||||
|     } |  | ||||||
|     menu.setGroupCheckable(MENU_GROUP_TRACKS, true, true); |  | ||||||
|     menu.findItem(player.getSelectedTrack(trackType) + ID_OFFSET).setChecked(true); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private static String buildTrackName(MediaFormat format) { |  | ||||||
|     if (format.adaptive) { |  | ||||||
|       return "auto"; |  | ||||||
|     } |  | ||||||
|     String trackName; |  | ||||||
|     if (MimeTypes.isVideo(format.mimeType)) { |  | ||||||
|       trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format), |  | ||||||
|           buildBitrateString(format)), buildTrackIdString(format)); |  | ||||||
|     } else if (MimeTypes.isAudio(format.mimeType)) { |  | ||||||
|       trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format), |  | ||||||
|           buildAudioPropertyString(format)), buildBitrateString(format)), |  | ||||||
|           buildTrackIdString(format)); |  | ||||||
|     } else { |  | ||||||
|       trackName = joinWithSeparator(joinWithSeparator(buildLanguageString(format), |  | ||||||
|           buildBitrateString(format)), buildTrackIdString(format)); |  | ||||||
|     } |  | ||||||
|     return trackName.length() == 0 ? "unknown" : trackName; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private static String buildResolutionString(MediaFormat format) { |  | ||||||
|     return format.width == MediaFormat.NO_VALUE || format.height == MediaFormat.NO_VALUE |  | ||||||
|         ? "" : format.width + "x" + format.height; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private static String buildAudioPropertyString(MediaFormat format) { |  | ||||||
|     return format.channelCount == MediaFormat.NO_VALUE || format.sampleRate == MediaFormat.NO_VALUE |  | ||||||
|         ? "" : format.channelCount + "ch, " + format.sampleRate + "Hz"; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private static String buildLanguageString(MediaFormat format) { |  | ||||||
|     return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? "" |  | ||||||
|         : format.language; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private static String buildBitrateString(MediaFormat format) { |  | ||||||
|     return format.bitrate == MediaFormat.NO_VALUE ? "" |  | ||||||
|         : String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private static String joinWithSeparator(String first, String second) { |  | ||||||
|     return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private static String buildTrackIdString(MediaFormat format) { |  | ||||||
|     return format.trackId == null ? "" : " (" + format.trackId + ")"; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private boolean onTrackItemClick(MenuItem item, int type) { |  | ||||||
|     if (player == null || item.getGroupId() != MENU_GROUP_TRACKS) { |  | ||||||
|       return false; |  | ||||||
|     } |  | ||||||
|     player.setSelectedTrack(type, item.getItemId() - ID_OFFSET); |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void toggleControlsVisibility()  { |  | ||||||
|     if (mediaController.isShowing()) { |  | ||||||
|       mediaController.hide(); |  | ||||||
|       debugRootView.setVisibility(View.GONE); |  | ||||||
|       playerStateTextView.setVisibility(View.GONE); |  | ||||||
|       debugTextView.setVisibility(View.GONE); |  | ||||||
|     } else { |  | ||||||
|       showControls(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void showControls() { |  | ||||||
|     mediaController.show(0); |  | ||||||
|     debugRootView.setVisibility(View.VISIBLE); |  | ||||||
|     playerStateTextView.setVisibility(View.VISIBLE); |  | ||||||
|     debugTextView.setVisibility(View.VISIBLE); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // NPExoPlayer.CaptionListener implementation |  | ||||||
|  |  | ||||||
|   @Override |  | ||||||
|   public void onCues(List<Cue> cues) { |  | ||||||
|     subtitleLayout.setCues(cues); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // NPExoPlayer.MetadataListener implementation |  | ||||||
|  |  | ||||||
|   @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())); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // SurfaceHolder.Callback implementation |  | ||||||
|  |  | ||||||
|   @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(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   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.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,755 @@ | |||||||
|  | /* | ||||||
|  |  * 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 | ||||||
|  |  * | ||||||
|  |  * 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 org.schabi.newpipe.player.exoplayer.SmoothStreamingTestMediaDrmCallback; | ||||||
|  | import org.schabi.newpipe.player.exoplayer.WidevineTestMediaDrmCallback; | ||||||
|  |  | ||||||
|  | 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.OnClickListener; | ||||||
|  | import android.view.View.OnKeyListener; | ||||||
|  | import android.view.View.OnTouchListener; | ||||||
|  | import android.view.accessibility.CaptioningManager; | ||||||
|  | import android.widget.Button; | ||||||
|  | import android.widget.MediaController; | ||||||
|  | import android.widget.PopupMenu; | ||||||
|  | import android.widget.PopupMenu.OnMenuItemClickListener; | ||||||
|  | import android.widget.TextView; | ||||||
|  | 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 debugRootView; | ||||||
|  |     private View shutterView; | ||||||
|  |     private AspectRatioFrameLayout videoFrame; | ||||||
|  |     private SurfaceView surfaceView; | ||||||
|  |     private TextView debugTextView; | ||||||
|  |     private TextView playerStateTextView; | ||||||
|  |     private SubtitleLayout subtitleLayout; | ||||||
|  |     private Button videoButton; | ||||||
|  |     private Button audioButton; | ||||||
|  |     private Button textButton; | ||||||
|  |     private Button retryButton; | ||||||
|  |  | ||||||
|  |     private NPExoPlayer player; | ||||||
|  |     private DebugTextViewHelper debugViewHelper; | ||||||
|  |     private boolean playerNeedsPrepare; | ||||||
|  |  | ||||||
|  |     private long playerPosition; | ||||||
|  |     private boolean enableBackgroundAudio; | ||||||
|  |  | ||||||
|  |     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; | ||||||
|  |             } | ||||||
|  |             playerStateTextView.setText(text); | ||||||
|  |             updateButtonVisibilities(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @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; | ||||||
|  |             updateButtonVisibilities(); | ||||||
|  |             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); | ||||||
|  |         debugRootView = findViewById(R.id.controls_root); | ||||||
|  |  | ||||||
|  |         videoFrame = (AspectRatioFrameLayout) findViewById(R.id.video_frame); | ||||||
|  |         surfaceView = (SurfaceView) findViewById(R.id.surface_view); | ||||||
|  |         surfaceView.getHolder().addCallback(surfaceHolderCallback); | ||||||
|  |         debugTextView = (TextView) findViewById(R.id.debug_text_view); | ||||||
|  |  | ||||||
|  |         playerStateTextView = (TextView) findViewById(R.id.player_state_view); | ||||||
|  |         subtitleLayout = (SubtitleLayout) findViewById(R.id.subtitles); | ||||||
|  |  | ||||||
|  |         mediaController = new KeyCompatibleMediaController(this); | ||||||
|  |         mediaController.setAnchorView(root); | ||||||
|  |         retryButton = (Button) findViewById(R.id.retry_button); | ||||||
|  |         videoButton = (Button) findViewById(R.id.video_controls); | ||||||
|  |         audioButton = (Button) findViewById(R.id.audio_controls); | ||||||
|  |         textButton = (Button) findViewById(R.id.text_controls); | ||||||
|  |  | ||||||
|  |         CookieHandler currentHandler = CookieHandler.getDefault(); | ||||||
|  |         if (currentHandler != defaultCookieManager) { | ||||||
|  |             CookieHandler.setDefault(defaultCookieManager); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, audioCapabilitiesListener); | ||||||
|  |         audioCapabilitiesReceiver.register(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         retryButton.setOnClickListener(new OnClickListener() { | ||||||
|  |             @Override | ||||||
|  |             public void onClick(View v) { | ||||||
|  |                 preparePlayer(true); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @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: | ||||||
|  |                 return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(), | ||||||
|  |                         new SmoothStreamingTestMediaDrmCallback()); | ||||||
|  |             case Util.TYPE_DASH: | ||||||
|  |                 return new DashRendererBuilder(this, userAgent, contentUri.toString(), | ||||||
|  |                         new WidevineTestMediaDrmCallback(contentId, provider)); | ||||||
|  |             case Util.TYPE_HLS: | ||||||
|  |                 return new HlsRendererBuilder(this, userAgent, contentUri.toString()); | ||||||
|  |             case Util.TYPE_OTHER: | ||||||
|  |                 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); | ||||||
|  |             debugViewHelper = new DebugTextViewHelper(player, debugTextView); | ||||||
|  |             playerStateTextView.setVisibility(View.GONE); | ||||||
|  |             debugTextView.setVisibility(View.GONE); | ||||||
|  |             debugViewHelper.start(); | ||||||
|  |         } | ||||||
|  |         if (playerNeedsPrepare) { | ||||||
|  |             player.prepare(); | ||||||
|  |             playerNeedsPrepare = false; | ||||||
|  |             updateButtonVisibilities(); | ||||||
|  |         } | ||||||
|  |         player.setSurface(surfaceView.getHolder().getSurface()); | ||||||
|  |         player.setPlayWhenReady(playWhenReady); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void releasePlayer() { | ||||||
|  |         if (player != null) { | ||||||
|  |             debugViewHelper.stop(); | ||||||
|  |             debugViewHelper = null; | ||||||
|  |             playerPosition = player.getCurrentPosition(); | ||||||
|  |             player.release(); | ||||||
|  |             player = null; | ||||||
|  |             eventLogger.endSession(); | ||||||
|  |             eventLogger = null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // User controls | ||||||
|  |  | ||||||
|  |     private void updateButtonVisibilities() { | ||||||
|  |         retryButton.setVisibility(playerNeedsPrepare ? View.VISIBLE : View.GONE); | ||||||
|  |         videoButton.setVisibility(haveTracks(NPExoPlayer.TYPE_VIDEO) ? View.VISIBLE : View.GONE); | ||||||
|  |         audioButton.setVisibility(haveTracks(NPExoPlayer.TYPE_AUDIO) ? View.VISIBLE : View.GONE); | ||||||
|  |         textButton.setVisibility(haveTracks(NPExoPlayer.TYPE_TEXT) ? View.VISIBLE : View.GONE); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private boolean haveTracks(int type) { | ||||||
|  |         return player != null && player.getTrackCount(type) > 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void showVideoPopup(View v) { | ||||||
|  |         PopupMenu popup = new PopupMenu(this, v); | ||||||
|  |         configurePopupWithTracks(popup, null, NPExoPlayer.TYPE_VIDEO); | ||||||
|  |         popup.show(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void showAudioPopup(View v) { | ||||||
|  |         PopupMenu popup = new PopupMenu(this, v); | ||||||
|  |         Menu menu = popup.getMenu(); | ||||||
|  |         menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.enable_background_audio); | ||||||
|  |         final MenuItem backgroundAudioItem = menu.findItem(0); | ||||||
|  |         backgroundAudioItem.setCheckable(true); | ||||||
|  |         backgroundAudioItem.setChecked(enableBackgroundAudio); | ||||||
|  |         OnMenuItemClickListener clickListener = new OnMenuItemClickListener() { | ||||||
|  |             @Override | ||||||
|  |             public boolean onMenuItemClick(MenuItem item) { | ||||||
|  |                 if (item == backgroundAudioItem) { | ||||||
|  |                     enableBackgroundAudio = !item.isChecked(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         configurePopupWithTracks(popup, clickListener, NPExoPlayer.TYPE_AUDIO); | ||||||
|  |         popup.show(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void showTextPopup(View v) { | ||||||
|  |         PopupMenu popup = new PopupMenu(this, v); | ||||||
|  |         configurePopupWithTracks(popup, null, NPExoPlayer.TYPE_TEXT); | ||||||
|  |         popup.show(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void showVerboseLogPopup(View v) { | ||||||
|  |         PopupMenu popup = new PopupMenu(this, v); | ||||||
|  |         Menu menu = popup.getMenu(); | ||||||
|  |         menu.add(Menu.NONE, 0, Menu.NONE, R.string.logging_normal); | ||||||
|  |         menu.add(Menu.NONE, 1, Menu.NONE, R.string.logging_verbose); | ||||||
|  |         menu.setGroupCheckable(Menu.NONE, true, true); | ||||||
|  |         menu.findItem((VerboseLogUtil.areAllTagsEnabled()) ? 1 : 0).setChecked(true); | ||||||
|  |         popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { | ||||||
|  |             @Override | ||||||
|  |             public boolean onMenuItemClick(MenuItem item) { | ||||||
|  |                 if (item.getItemId() == 0) { | ||||||
|  |                     VerboseLogUtil.setEnableAllTags(false); | ||||||
|  |                 } else { | ||||||
|  |                     VerboseLogUtil.setEnableAllTags(true); | ||||||
|  |                 } | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         popup.show(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void configurePopupWithTracks(PopupMenu popup, | ||||||
|  |                                           final OnMenuItemClickListener customActionClickListener, | ||||||
|  |                                           final int trackType) { | ||||||
|  |         if (player == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         int trackCount = player.getTrackCount(trackType); | ||||||
|  |         if (trackCount == 0) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { | ||||||
|  |             @Override | ||||||
|  |             public boolean onMenuItemClick(MenuItem item) { | ||||||
|  |                 return (customActionClickListener != null | ||||||
|  |                         && customActionClickListener.onMenuItemClick(item)) | ||||||
|  |                         || onTrackItemClick(item, trackType); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         Menu menu = popup.getMenu(); | ||||||
|  |         // ID_OFFSET ensures we avoid clashing with Menu.NONE (which equals 0). | ||||||
|  |         menu.add(MENU_GROUP_TRACKS, NPExoPlayer.TRACK_DISABLED + ID_OFFSET, Menu.NONE, R.string.off); | ||||||
|  |         for (int i = 0; i < trackCount; i++) { | ||||||
|  |             menu.add(MENU_GROUP_TRACKS, i + ID_OFFSET, Menu.NONE, | ||||||
|  |                     buildTrackName(player.getTrackFormat(trackType, i))); | ||||||
|  |         } | ||||||
|  |         menu.setGroupCheckable(MENU_GROUP_TRACKS, true, true); | ||||||
|  |         menu.findItem(player.getSelectedTrack(trackType) + ID_OFFSET).setChecked(true); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String buildTrackName(MediaFormat format) { | ||||||
|  |         if (format.adaptive) { | ||||||
|  |             return "auto"; | ||||||
|  |         } | ||||||
|  |         String trackName; | ||||||
|  |         if (MimeTypes.isVideo(format.mimeType)) { | ||||||
|  |             trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format), | ||||||
|  |                     buildBitrateString(format)), buildTrackIdString(format)); | ||||||
|  |         } else if (MimeTypes.isAudio(format.mimeType)) { | ||||||
|  |             trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format), | ||||||
|  |                             buildAudioPropertyString(format)), buildBitrateString(format)), | ||||||
|  |                     buildTrackIdString(format)); | ||||||
|  |         } else { | ||||||
|  |             trackName = joinWithSeparator(joinWithSeparator(buildLanguageString(format), | ||||||
|  |                     buildBitrateString(format)), buildTrackIdString(format)); | ||||||
|  |         } | ||||||
|  |         return trackName.length() == 0 ? "unknown" : trackName; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String buildResolutionString(MediaFormat format) { | ||||||
|  |         return format.width == MediaFormat.NO_VALUE || format.height == MediaFormat.NO_VALUE | ||||||
|  |                 ? "" : format.width + "x" + format.height; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String buildAudioPropertyString(MediaFormat format) { | ||||||
|  |         return format.channelCount == MediaFormat.NO_VALUE || format.sampleRate == MediaFormat.NO_VALUE | ||||||
|  |                 ? "" : format.channelCount + "ch, " + format.sampleRate + "Hz"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String buildLanguageString(MediaFormat format) { | ||||||
|  |         return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? "" | ||||||
|  |                 : format.language; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String buildBitrateString(MediaFormat format) { | ||||||
|  |         return format.bitrate == MediaFormat.NO_VALUE ? "" | ||||||
|  |                 : String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String joinWithSeparator(String first, String second) { | ||||||
|  |         return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String buildTrackIdString(MediaFormat format) { | ||||||
|  |         return format.trackId == null ? "" : " (" + format.trackId + ")"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private boolean onTrackItemClick(MenuItem item, int type) { | ||||||
|  |         if (player == null || item.getGroupId() != MENU_GROUP_TRACKS) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         player.setSelectedTrack(type, item.getItemId() - ID_OFFSET); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void toggleControlsVisibility()  { | ||||||
|  |         if (mediaController.isShowing()) { | ||||||
|  |             mediaController.hide(); | ||||||
|  |             debugRootView.setVisibility(View.GONE); | ||||||
|  |             playerStateTextView.setVisibility(View.GONE); | ||||||
|  |             debugTextView.setVisibility(View.GONE); | ||||||
|  |         } else { | ||||||
|  |             showControls(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void showControls() { | ||||||
|  |         mediaController.show(0); | ||||||
|  |         debugRootView.setVisibility(View.VISIBLE); | ||||||
|  |         playerStateTextView.setVisibility(View.VISIBLE); | ||||||
|  |         debugTextView.setVisibility(View.VISIBLE); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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 | ||||||
| @@ -13,9 +13,9 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| package org.schabi.newpipe.exoplayer; | package org.schabi.newpipe.player.exoplayer; | ||||||
| 
 | 
 | ||||||
| import org.schabi.newpipe.exoplayer.NPExoPlayer.RendererBuilder; | import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder; | ||||||
| 
 | 
 | ||||||
| import com.google.android.exoplayer.DefaultLoadControl; | import com.google.android.exoplayer.DefaultLoadControl; | ||||||
| import com.google.android.exoplayer.LoadControl; | import com.google.android.exoplayer.LoadControl; | ||||||
| @@ -13,7 +13,7 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| package org.schabi.newpipe.exoplayer; | package org.schabi.newpipe.player.exoplayer; | ||||||
| 
 | 
 | ||||||
| import com.google.android.exoplayer.ExoPlayer; | import com.google.android.exoplayer.ExoPlayer; | ||||||
| import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; | import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; | ||||||
| @@ -13,9 +13,9 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| package org.schabi.newpipe.exoplayer; | package org.schabi.newpipe.player.exoplayer; | ||||||
| 
 | 
 | ||||||
| import org.schabi.newpipe.exoplayer.NPExoPlayer.RendererBuilder; | import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder; | ||||||
| 
 | 
 | ||||||
| import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; | import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; | ||||||
| import com.google.android.exoplayer.MediaCodecSelector; | import com.google.android.exoplayer.MediaCodecSelector; | ||||||
| @@ -13,9 +13,9 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| package org.schabi.newpipe.exoplayer; | package org.schabi.newpipe.player.exoplayer; | ||||||
| 
 | 
 | ||||||
| import org.schabi.newpipe.exoplayer.NPExoPlayer.RendererBuilder; | import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder; | ||||||
| 
 | 
 | ||||||
| import com.google.android.exoplayer.DefaultLoadControl; | import com.google.android.exoplayer.DefaultLoadControl; | ||||||
| import com.google.android.exoplayer.LoadControl; | import com.google.android.exoplayer.LoadControl; | ||||||
| @@ -13,7 +13,7 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| package org.schabi.newpipe.exoplayer; | package org.schabi.newpipe.player.exoplayer; | ||||||
| 
 | 
 | ||||||
| import com.google.android.exoplayer.CodecCounters; | import com.google.android.exoplayer.CodecCounters; | ||||||
| import com.google.android.exoplayer.DummyTrackRenderer; | import com.google.android.exoplayer.DummyTrackRenderer; | ||||||
| @@ -13,10 +13,10 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| package org.schabi.newpipe.exoplayer; | package org.schabi.newpipe.player.exoplayer; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| import org.schabi.newpipe.exoplayer.NPExoPlayer.RendererBuilder; | import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder; | ||||||
| 
 | 
 | ||||||
| import com.google.android.exoplayer.DefaultLoadControl; | import com.google.android.exoplayer.DefaultLoadControl; | ||||||
| import com.google.android.exoplayer.LoadControl; | import com.google.android.exoplayer.LoadControl; | ||||||
| @@ -13,7 +13,7 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| package org.schabi.newpipe.exoplayer; | package org.schabi.newpipe.player.exoplayer; | ||||||
| 
 | 
 | ||||||
| import com.google.android.exoplayer.drm.MediaDrmCallback; | import com.google.android.exoplayer.drm.MediaDrmCallback; | ||||||
| import com.google.android.exoplayer.drm.StreamingDrmSessionManager; | import com.google.android.exoplayer.drm.StreamingDrmSessionManager; | ||||||
| @@ -13,7 +13,7 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| package org.schabi.newpipe.exoplayer; | package org.schabi.newpipe.player.exoplayer; | ||||||
| 
 | 
 | ||||||
| import com.google.android.exoplayer.drm.MediaDrmCallback; | import com.google.android.exoplayer.drm.MediaDrmCallback; | ||||||
| import com.google.android.exoplayer.util.Util; | import com.google.android.exoplayer.util.Util; | ||||||
| @@ -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" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Christian Schabesberger
					Christian Schabesberger