| /* |
| * Copyright 2017 Google Inc. All Rights Reserved. |
| * |
| * 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 com.google.vr.sdk.samples.videoplayer; |
| |
| import android.content.Context; |
| import android.net.Uri; |
| import android.util.Log; |
| import android.view.Surface; |
| import com.google.android.exoplayer2.C; |
| import com.google.android.exoplayer2.DefaultRenderersFactory; |
| import com.google.android.exoplayer2.ExoPlaybackException; |
| import com.google.android.exoplayer2.ExoPlayerFactory; |
| import com.google.android.exoplayer2.PlaybackParameters; |
| import com.google.android.exoplayer2.Player; |
| import com.google.android.exoplayer2.SimpleExoPlayer; |
| import com.google.android.exoplayer2.Timeline; |
| import com.google.android.exoplayer2.audio.AudioProcessor; |
| import com.google.android.exoplayer2.decoder.DecoderCounters; |
| import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; |
| import com.google.android.exoplayer2.drm.DrmSessionManager; |
| import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; |
| import com.google.android.exoplayer2.drm.FrameworkMediaDrm; |
| import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; |
| import com.google.android.exoplayer2.drm.UnsupportedDrmException; |
| import com.google.android.exoplayer2.ext.gvr.GvrAudioProcessor; |
| import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; |
| import com.google.android.exoplayer2.source.ClippingMediaSource; |
| import com.google.android.exoplayer2.source.ExtractorMediaSource; |
| import com.google.android.exoplayer2.source.MediaSource; |
| import com.google.android.exoplayer2.source.TrackGroupArray; |
| import com.google.android.exoplayer2.source.dash.DashMediaSource; |
| import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; |
| import com.google.android.exoplayer2.source.hls.HlsMediaSource; |
| import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; |
| import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; |
| import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; |
| import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; |
| import com.google.android.exoplayer2.trackselection.TrackSelection; |
| import com.google.android.exoplayer2.trackselection.TrackSelectionArray; |
| import com.google.android.exoplayer2.upstream.DataSource; |
| import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; |
| import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; |
| import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; |
| import com.google.android.exoplayer2.upstream.HttpDataSource; |
| import com.google.android.exoplayer2.util.Util; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Wrapper for ExoPlayer2 functionality. Handles all the necessary setup for video playback and |
| * provides access to simple video playback controls. Plays unprotected videos from a specified Uri, |
| * and plays DRM video under the Widevine test license. |
| */ |
| public class VideoExoPlayer2 implements Player.EventListener { |
| |
| private static final String TAG = VideoExoPlayer2.class.getSimpleName(); |
| private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); |
| |
| private final Context context; |
| private final Settings settings; |
| private final String userAgent; |
| private final DataSource.Factory mediaDataSourceFactory; |
| private GvrAudioProcessor gvrAudioProcessor; |
| private SimpleExoPlayer player; |
| |
| public VideoExoPlayer2(Context context, Settings settings) { |
| this.context = context; |
| this.settings = settings; |
| userAgent = Util.getUserAgent(context, "VideoExoPlayer"); |
| mediaDataSourceFactory = buildDataSourceFactory(true); |
| } |
| |
| public void play() { |
| if (player != null) { |
| player.setPlayWhenReady(true); |
| } |
| } |
| |
| public void pause() { |
| if (player != null) { |
| player.setPlayWhenReady(false); |
| } |
| } |
| |
| public void togglePause() { |
| if (isPaused()) { |
| play(); |
| } else { |
| pause(); |
| } |
| } |
| |
| public boolean isPaused() { |
| return !player.getPlayWhenReady(); |
| } |
| |
| public SimpleExoPlayer getPlayer() { |
| return player; |
| } |
| |
| /** |
| * Returns the {@link GvrAudioProcessor} the player is using to render spatial audio, or |
| * {@code null} if not set. The audio processor is part of the ExoPlayer GVR extension. |
| */ |
| public GvrAudioProcessor getGvrAudioProcessor() { |
| return gvrAudioProcessor; |
| } |
| |
| /** |
| * Sets the Surface for the video player to decode frames into. When the Surface is set, the video |
| * player will begin to autoplay. |
| * |
| * @param surface The Surface to consume video frames. |
| */ |
| public void setSurface(Surface surface) { |
| if (player != null) { |
| player.setVideoSurface(surface); |
| } else { |
| Log.e(TAG, "Error: video player has not been initialized. Cannot set Surface."); |
| } |
| } |
| |
| /** |
| * Initializes the video player. The player can playback a video at the specified Uri. DRM videos |
| * hosted by the Widevine test license can be played |
| * |
| * <p>Note: this should be called in the host activity's onStart() method. |
| * |
| * @param uri The uri of the source video to play. |
| * @param optionalDrmVideoId The ID of the video under the Widevine test license. |
| */ |
| public void initPlayer(Uri uri, String optionalDrmVideoId) |
| throws UnsupportedDrmException { |
| DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null; |
| if (optionalDrmVideoId != null) { |
| try { |
| drmSessionManager = |
| buildDrmSessionManager( |
| C.WIDEVINE_UUID, |
| getWidevineTestLicenseUrl(optionalDrmVideoId)); |
| } catch (UnsupportedDrmException e) { |
| String errorString = |
| e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME |
| ? "UnsupportedDrmException" |
| : "UnknownDrmException"; |
| Log.e(TAG, "Error: " + errorString, e); |
| throw e; |
| } |
| } |
| |
| TrackSelection.Factory videoTrackSelectionFactory = |
| new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); |
| DefaultTrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); |
| |
| gvrAudioProcessor = new GvrAudioProcessor(); |
| player = ExoPlayerFactory.newSimpleInstance( |
| new GvrRenderersFactory(context, drmSessionManager, gvrAudioProcessor), |
| trackSelector); |
| player.addListener(this); |
| |
| // Auto play the video. |
| player.setPlayWhenReady(true); |
| |
| MediaSource mediaSource = buildMediaSource(uri); |
| if (settings.videoLengthSeconds > 0) { |
| long lengthMicroseconds = TimeUnit.SECONDS.toMicros(settings.videoLengthSeconds); |
| mediaSource = new ClippingMediaSource(mediaSource, 0, lengthMicroseconds, false); |
| } |
| // Prepare the player with the source. |
| player.prepare(mediaSource); |
| } |
| |
| /** |
| * Releases the video player. Once the player is released, the client must call initPlayer() |
| * before the video player can be used again. |
| * |
| * <p>Note: this should be called in the host activity's onStop(). |
| */ |
| public void releasePlayer() { |
| if (player != null) { |
| player.release(); |
| player = null; |
| gvrAudioProcessor = null; |
| } |
| } |
| |
| private static String getWidevineTestLicenseUrl(String id) { |
| return "https://proxy.uat.widevine.com/proxy?video_id=" + id + "&provider=widevine_test"; |
| } |
| |
| /** |
| * Returns a new DataSource factory. |
| * |
| * @param useBandwidthMeter Whether to set {@link #BANDWIDTH_METER} as a listener to the new |
| * DataSource factory. |
| * @return A new DataSource factory. |
| */ |
| private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) { |
| return buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null); |
| } |
| |
| private DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) { |
| return new DefaultDataSourceFactory( |
| context, bandwidthMeter, buildHttpDataSourceFactory(bandwidthMeter)); |
| } |
| |
| /** |
| * Returns a new HttpDataSource factory. |
| * |
| * @param useBandwidthMeter Whether to set {@link #BANDWIDTH_METER} as a listener to the new |
| * DataSource factory. |
| * @return A new HttpDataSource factory. |
| */ |
| private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) { |
| return buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null); |
| } |
| |
| private HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) { |
| return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter); |
| } |
| |
| private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager( |
| UUID uuid, String licenseUrl) throws UnsupportedDrmException { |
| if (Util.SDK_INT < 18) { |
| return null; |
| } |
| HttpMediaDrmCallback drmCallback = |
| new HttpMediaDrmCallback(licenseUrl, buildHttpDataSourceFactory(false)); |
| return new DefaultDrmSessionManager<>( |
| uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback, null, null, null); |
| } |
| |
| private MediaSource buildMediaSource(Uri uri) { |
| int type = Util.inferContentType(uri.getLastPathSegment()); |
| switch (type) { |
| case C.TYPE_SS: |
| return new SsMediaSource( |
| uri, |
| buildDataSourceFactory(false), |
| new DefaultSsChunkSource.Factory(mediaDataSourceFactory), |
| null, |
| null); |
| case C.TYPE_DASH: |
| return new DashMediaSource( |
| uri, |
| buildDataSourceFactory(false), |
| new DefaultDashChunkSource.Factory(mediaDataSourceFactory), |
| null, |
| null); |
| case C.TYPE_HLS: |
| return new HlsMediaSource(uri, mediaDataSourceFactory, null, null); |
| case C.TYPE_OTHER: |
| return new ExtractorMediaSource( |
| uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), null, null); |
| default: |
| { |
| throw new IllegalStateException("Unsupported type: " + type); |
| } |
| } |
| } |
| |
| private static final class GvrRenderersFactory extends DefaultRenderersFactory { |
| |
| private final GvrAudioProcessor gvrAudioProcessor; |
| |
| private GvrRenderersFactory(Context context, |
| DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, |
| GvrAudioProcessor gvrAudioProcessor) { |
| super(context, drmSessionManager); |
| this.gvrAudioProcessor = gvrAudioProcessor; |
| } |
| |
| @Override |
| public AudioProcessor[] buildAudioProcessors() { |
| return new AudioProcessor[] {gvrAudioProcessor}; |
| } |
| |
| } |
| |
| @Override |
| public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { |
| if (playbackState == Player.STATE_ENDED) { |
| pause(); |
| Log.i(TAG, "Total video frames decoded: " + getRenderedOutputBufferCount()); |
| } |
| } |
| |
| protected int getRenderedOutputBufferCount() { |
| DecoderCounters counters = player.getVideoDecoderCounters(); |
| counters.ensureUpdated(); |
| return counters.renderedOutputBufferCount; |
| } |
| |
| @Override |
| public void onLoadingChanged(boolean isLoading) {} |
| @Override |
| public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {} |
| @Override |
| public void onPlayerError(ExoPlaybackException error) {} |
| |
| @Override |
| public void onPositionDiscontinuity(int reason) {} |
| |
| @Override |
| public void onRepeatModeChanged(int repeatMode) {} |
| |
| @Override |
| public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {} |
| |
| // Old API. |
| public void onTimelineChanged(Timeline timeline, Object manifest) {} |
| // New API. |
| public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {} |
| |
| @Override |
| public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} |
| |
| @Override |
| public void onSeekProcessed() {} |
| } |