From c233fc90e57e2e3c908869fa8ba9a35408589514 Mon Sep 17 00:00:00 2001 From: Chris Thompson Date: Mon, 8 Jul 2019 19:47:50 -0400 Subject: [PATCH 1/4] Add google's maven url --- Application/gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/Application/gradle.properties b/Application/gradle.properties index 2e1f9da..00285ff 100644 --- a/Application/gradle.properties +++ b/Application/gradle.properties @@ -24,3 +24,4 @@ applicationName = FireAppBuilder # mediaPlayerType = externalExoPlayer # compileUrl = com.brightcove.player:exoplayer:5.0.3 # mavenUrl = http://repo.brightcove.com/releases +mavenUrl = https://maven.google.com/ From a81b07e645ecf43ce2172c64705b6b42c16a3cbd Mon Sep 17 00:00:00 2001 From: Chris Thompson Date: Fri, 12 Jul 2019 13:44:52 -0400 Subject: [PATCH 2/4] Add ExoPlayer2 glue code --- ExoPlayer2MediaPlayerComponent/build.gradle | 69 ++ .../proguard-rules.pro | 17 + .../src/main/AndroidManifest.xml | 29 + .../glue/ExoPlayer2MediaPlayer.java | 1031 +++++++++++++++++ .../ExoPlayer2MediaPlayerImplCreator.java | 36 + .../src/main/java/com/amazon/tv/Settings.java | 34 + .../src/main/res/values-en-rUS/strings.xml | 16 + .../src/main/res/values/strings.xml | 16 + 8 files changed, 1248 insertions(+) create mode 100644 ExoPlayer2MediaPlayerComponent/build.gradle create mode 100644 ExoPlayer2MediaPlayerComponent/proguard-rules.pro create mode 100644 ExoPlayer2MediaPlayerComponent/src/main/AndroidManifest.xml create mode 100644 ExoPlayer2MediaPlayerComponent/src/main/java/com/amazon/mediaplayer/glue/ExoPlayer2MediaPlayer.java create mode 100644 ExoPlayer2MediaPlayerComponent/src/main/java/com/amazon/mediaplayer/module/ExoPlayer2MediaPlayerImplCreator.java create mode 100644 ExoPlayer2MediaPlayerComponent/src/main/java/com/amazon/tv/Settings.java create mode 100644 ExoPlayer2MediaPlayerComponent/src/main/res/values-en-rUS/strings.xml create mode 100644 ExoPlayer2MediaPlayerComponent/src/main/res/values/strings.xml diff --git a/ExoPlayer2MediaPlayerComponent/build.gradle b/ExoPlayer2MediaPlayerComponent/build.gradle new file mode 100644 index 0000000..70a6027 --- /dev/null +++ b/ExoPlayer2MediaPlayerComponent/build.gradle @@ -0,0 +1,69 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. 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. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "24.0.3" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + multiDexEnabled true + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + debug { + testCoverageEnabled true + } + } + packagingOptions { + pickFirst 'META-INF/LICENSE' + } + //compileOptions { + // sourceCompatibility JavaVersion.VERSION_1_8 + // targetCompatibility JavaVersion.VERSION_1_8 + //} +} + +apply plugin: 'jacoco' +jacoco { + version "0.7.1.201405082137" +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:support-v4:23.1.1' + compile 'com.amazon.android:exoplayer:2.7.1' + compile project(':ModuleInterface') + compile project(':UAMP') + + // Set this dependency to use JUnit 4 rules + androidTestCompile 'com.android.support.test:rules:0.4.1' + androidTestCompile('junit:junit:4.12') + + androidTestCompile('com.android.support.test:runner:0.4.1') { + exclude module: 'support-annotations' + } +} diff --git a/ExoPlayer2MediaPlayerComponent/proguard-rules.pro b/ExoPlayer2MediaPlayerComponent/proguard-rules.pro new file mode 100644 index 0000000..ad22c49 --- /dev/null +++ b/ExoPlayer2MediaPlayerComponent/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/urgcelil/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/ExoPlayer2MediaPlayerComponent/src/main/AndroidManifest.xml b/ExoPlayer2MediaPlayerComponent/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7d1dd55 --- /dev/null +++ b/ExoPlayer2MediaPlayerComponent/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/ExoPlayer2MediaPlayerComponent/src/main/java/com/amazon/mediaplayer/glue/ExoPlayer2MediaPlayer.java b/ExoPlayer2MediaPlayerComponent/src/main/java/com/amazon/mediaplayer/glue/ExoPlayer2MediaPlayer.java new file mode 100644 index 0000000..fc727b4 --- /dev/null +++ b/ExoPlayer2MediaPlayerComponent/src/main/java/com/amazon/mediaplayer/glue/ExoPlayer2MediaPlayer.java @@ -0,0 +1,1031 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. 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. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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.amazon.mediaplayer.glue; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Settings; +import android.support.annotation.Nullable; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.widget.FrameLayout; + +import com.amazon.android.uamp.UAMP; +import com.amazon.mediaplayer.AMZNMediaPlayer; +import com.amazon.mediaplayer.playback.SeekRange; +import com.amazon.mediaplayer.playback.config.BaseContentPlaybackBufferConfig; +import com.amazon.mediaplayer.tracks.MediaFormat; +import com.amazon.mediaplayer.tracks.TrackType; +import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Player.DiscontinuityReason; +import com.google.android.exoplayer2.RenderersFactory; +import com.google.android.exoplayer2.DefaultRenderersFactory; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; +import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.source.dash.DashChunkSource; +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.text.Cue; +import com.google.android.exoplayer2.text.TextOutput; +import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.upstream.BandwidthMeter; +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.upstream.TransferListener; +import com.google.android.exoplayer2.util.EventLogger; +import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoListener; + +import java.util.ArrayList; +import java.util.EventListener; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static android.provider.Settings.Global.getInt; +import static com.amazon.tv.Settings.Global.VIDEO_QUALITY; +import static com.amazon.tv.Settings.Global.VIDEO_QUALITY_BEST; +import static com.amazon.tv.Settings.Global.VIDEO_QUALITY_BETTER; +import static com.amazon.tv.Settings.Global.VIDEO_QUALITY_GOOD; + +/** + * Amazon Media Player glue class. + */ +public class ExoPlayer2MediaPlayer implements UAMP, SurfaceHolder.Callback, EventListener, VideoListener, TextOutput { + + /** + * Debug TAG. + */ + private static final String TAG = ExoPlayer2MediaPlayer.class.getSimpleName(); + + /** + * The bandwidth cap value that will represent VIDEO_QUALITY_GOOD setting option, valued at + * 1350kbps. + * TODO: These values are only initial estimates. We need to get the final versions from PM + * or find a dynamic way to get the values from the device. + */ + private static final int GOOD_BITRATE = 1350000; + + /** + * The bandwidth cap value that will represent the VIDEO_QUALITY_BETTER setting option, valued + * at 4000kbps. + * TODO: These values are only initial estimates. We need to get the final versions from PM + * or find a dynamic way to get the values from the device. + */ + private static final int BETTER_BITRATE = 4000000; + + /** + * Pass through bundle. + */ + private Bundle mExtras; + + /** + * Internal reference to FrameLayout. + */ + private FrameLayout mFrameLayout; + + private SimpleExoPlayer mPlayer; + private DataSource.Factory mDataSourceFactory; + private MediaSource mCurrentMediaSource; + private PlayerState mPlayerState; + private int mVideoWidth = -1; + private int mVideoHeight = -1; + private float mVideoAspect = 1.0f; + private DefaultTrackSelector mTrackSelector; + private TrackSelection.Factory mTrackSelectionFactory; + private Timeline mCurrentTimeline; + + private Set mErrorListeners; + private Set mStateListeners; + private Set mCuesListeners; + private Set mInfoListeners; + + /** + * Static bandwidth meter so that we get a universal view from all transfers. + */ + private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); + + private Handler mMediaSourceHandler; + private EventLogger eventLogger; + + + + /** + * Internal reference to context. + */ + private Context mContext; + + /** + * SurfaceView for video playback. + */ + private SurfaceView mSurfaceView; + + /** + * Flag for Surface View attached status. + */ + private boolean mSurfaceViewAttached = false; + + public ExoPlayer2MediaPlayer() { + super(); + mErrorListeners = new HashSet<>(); + mStateListeners = new HashSet<>(); + mCuesListeners = new HashSet<>(); + mInfoListeners = new HashSet<>(); + mMediaSourceHandler = new Handler(); + } + + protected String mUserAgent; + + /** + * {@inheritDoc} + */ + @Override + public boolean canRenderCC() { + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canRenderAds() { + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void surfaceCreated(SurfaceHolder holder) { + + Log.d(TAG, "surface created"); + // Must handle this. + this.setSurface(holder.getSurface(), false); // Non blocking. + } + + /** + * {@inheritDoc} + */ + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + + Log.d(TAG, "surface changed"); + } + + /** + * {@inheritDoc} + */ + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + + Log.d(TAG, "surface destroyed"); + this.setSurface(null, true); // Blocking + } + + /** + * {@inheritDoc} + */ + @Override + public void attachSurfaceView() { + Log.d(TAG, "attachSurfaceView"); + if (!mSurfaceViewAttached) { + mFrameLayout.addView(mSurfaceView); + mSurfaceViewAttached = true; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void detachSurfaceView() { + Log.d(TAG, "detachSurfaceView"); + if (mSurfaceViewAttached) { + mFrameLayout.removeView(mSurfaceView); + mSurfaceViewAttached = false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void init(Context context, FrameLayout frameLayout, Bundle extras) { + Log.v(TAG, "init"); + mContext = context; + mFrameLayout = frameLayout; + mExtras = extras; + mUserAgent = "CustomExoPlayer"; + + mDataSourceFactory = buildDataSourceFactory(true); + + /* + * AdaptiveTrackSelection must be driven by the same instance of + * DefaultBandwidthMeter that is set to listen to media source + */ + mTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); + mTrackSelector = new DefaultTrackSelector(mTrackSelectionFactory); + eventLogger = new EventLogger(mTrackSelector); + + mPlayer = ExoPlayerFactory.newSimpleInstance(mContext, mTrackSelector); + mPlayer.addListener(eventLogger); + mPlayer.addListener(new PlayerEventListener()); + mPlayer.addVideoListener(this); + mPlayer.addTextOutput(this); + mPlayer.addMetadataOutput(eventLogger); + mPlayer.addAudioDebugListener(eventLogger); + mPlayer.addVideoDebugListener(eventLogger); + setVideoQuality(); + + Player.VideoComponent newVideoComponent = mPlayer.getVideoComponent(); + mSurfaceView = new SurfaceView(mContext); + mSurfaceView.getHolder().addCallback(this); + mFrameLayout.addView(mSurfaceView); + mSurfaceViewAttached = true; + mPlayer.setVideoSurfaceView(mSurfaceView); + mPlayerState = PlayerState.IDLE; + } + + @Override + public void onCues(List cues) { + Log.v(TAG, "onCues"); + if (cues != null) { + List amznCues = new ArrayList(); + for (Cue cue : cues) { + com.amazon.mediaplayer.playback.text.Cue amznCue = new com.amazon.mediaplayer.playback.text.Cue( + cue.text, + cue.textAlignment, + cue.line, + cue.lineType, + cue.lineAnchor, + cue.position, + cue.positionAnchor, + cue.size + ); + amznCues.add(amznCue); + } + for (OnCuesListener cuesListener : mCuesListeners) { + cuesListener.onCues(amznCues); + } + } + } + + /** + * 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) { + TransferListener listener = useBandwidthMeter ? BANDWIDTH_METER : null; + return new DefaultDataSourceFactory(mContext, listener, + buildHttpDataSourceFactory(useBandwidthMeter)); + } + + /** + * 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) { + TransferListener listener = useBandwidthMeter ? BANDWIDTH_METER : null; + return new DefaultHttpDataSourceFactory(mUserAgent, listener); + } + + private class PlayerEventListener implements Player.EventListener { + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + Log.v(TAG, "onPlayerStateChanged " + playbackState); + // Convert ExoPlayer states to AMZNPlayer states + switch (playbackState) { + case Player.STATE_IDLE: + Log.v(TAG, "STATE_IDLE"); + setPlayerState(PlayerState.IDLE); + break; + + case Player.STATE_BUFFERING: + Log.v(TAG, "STATE_BUFFERING"); + setPlayerState(PlayerState.BUFFERING); + break; + + case Player.STATE_READY: + Log.v(TAG, "STATE_READY"); + if (playWhenReady) { + setPlayerState(PlayerState.PLAYING); + } + else { + setPlayerState(PlayerState.READY); + } + break; + + case Player.STATE_ENDED: + Log.v(TAG, "STATE_ENDED"); + setPlayerState(PlayerState.ENDED); + break; + } + } + + @Override + public void onRepeatModeChanged(int repeatMode) { + Log.v(TAG, "onRepeatModeChanged"); + } + + @Override + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { + Log.v(TAG, "onShuffleModeEnabledChanged"); + } + + @Override + public void onPlayerError(ExoPlaybackException e) { + Log.v(TAG, "onPlayerError"); + setPlayerState(PlayerState.ERROR); + for (OnErrorListener errorListener : mErrorListeners) { + errorListener.onError(new Error(ErrorType.PLAYER_ERROR, e, null)); + } + } + + @Override + public void onPositionDiscontinuity(int reason) { + Log.v(TAG, "onPositionDiscontinuity " + reason); + } + + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + Log.v(TAG, "onPlaybackParametersChanged"); + } + + @Override + public void onSeekProcessed() { + Log.v(TAG, "onSeekProcessed"); + } + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { + Log.v(TAG, "onTimelineChanged "); + mCurrentTimeline = timeline; + } + + @Override + @SuppressWarnings("ReferenceEquality") + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + Log.v(TAG, "onTracksChanged"); + } + + @Override + public void onLoadingChanged(boolean isLoading) { + Log.v(TAG, "onLoadingChanged"); + } + } + + /** + * Reads user settings for any bit rate caps set by user and sends that value to player. + */ + private void setVideoQuality() { + + int videoQualityValue = readVideoQualityValue(); + // Set the bandwidth cap only if we get a valid cap on video quality, else let the player + // use the default value. + if (videoQualityValue != -1) { + // TODO: set mMaxBitrateCap in ExoAdaptivePlaybackBufferConfig and + // ExoExtendedAdaptivePlaybackBufferConfig + // Like: + // mPlayer.setContentBufferConfig(adaptiveBufConfig); + } + } + + /** + * Reads the video quality type from settings and returns the corresponding video quality + * value. Returns -1 if no specific value is set in the settings. + * + * @return video quality bit rate value, -1 if no specific value is set. + */ + private int readVideoQualityValue() { + + int defaultVideoQualityType = VIDEO_QUALITY_BEST; + // Try to retrieve the video quality setting from global settings. + try { + defaultVideoQualityType = getInt(mContext.getContentResolver(), + VIDEO_QUALITY); + } + catch (Settings.SettingNotFoundException e) { + Log.i(TAG, "Settings do not contain any video quality preferences"); + } + // Set the bandwidth cap only if we get a valid cap on video quality, else let the player + // use the default value. + switch (defaultVideoQualityType) { + case VIDEO_QUALITY_GOOD: + return GOOD_BITRATE; + case VIDEO_QUALITY_BETTER: + return BETTER_BITRATE; + default: + return -1; + } + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle getExtra() { + return mExtras; + } + + /** + * {@inheritDoc} + */ + @Override + public void addErrorListener(OnErrorListener onErrorListener) { + Log.v(TAG, "addErrorListener"); + if (!mErrorListeners.contains(onErrorListener)) { + mErrorListeners.add(onErrorListener); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeErrorListener(OnErrorListener onErrorListener) { + Log.v(TAG, "removeErrorListener"); + mErrorListeners.remove(onErrorListener); + } + + /** + * NOTE: FAB PlaybackActivity does not use this + */ + @Override + public void addInfoListener(OnInfoListener onInfoListener) { + Log.v(TAG, "addInfoListener"); + if (!mInfoListeners.contains(onInfoListener)) { + mInfoListeners.add(onInfoListener); + } + } + + /** + * NOTE: FAB PlaybackActivity does not use this + */ + @Override + public void removeInfoListener(OnInfoListener onInfoListener) { + Log.v(TAG, "removeInfoListener"); + mInfoListeners.remove(onInfoListener); + } + + /** + * {@inheritDoc} + */ + @Override + public void addStateChangeListener(OnStateChangeListener onStateChangeListener) { + Log.v(TAG, "addStateChangeListener"); + if (!mStateListeners.contains(onStateChangeListener)) { + mStateListeners.add(onStateChangeListener); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeStateChangeListener(OnStateChangeListener onStateChangeListener) { + Log.v(TAG, "removeStateChangeListener"); + mStateListeners.remove(onStateChangeListener); + } + + /** + * NOTE: FAB PlaybackActivity does not use this + */ + @Override + public void setHlsAdaptiveSwitchMode(AdaptiveSwitchMode adaptiveSwitchMode) { + Log.w(TAG, "setHlsAdaptiveSwitchMode not implemented"); + } + + /** + * NOTE: FAB PlaybackActivity does not use this + */ + @Override + public void setContentBufferConfig(BaseContentPlaybackBufferConfig + baseContentPlaybackBufferConfig) { + Log.w(TAG, "setContentBufferConfig not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public void open(ContentParameters contentParameters) { + Log.v(TAG, "open " + contentParameters.url); + mPlayer.setPlayWhenReady(true); + + if (contentParameters.url == null || contentParameters.url.isEmpty()) { + Log.w(TAG, "Invalid or missing URL in ContentParameters"); + return; + } + + Uri url = Uri.parse(contentParameters.url); + + int mediaSourceType = -1; + + if (contentParameters.mimeType != null) { + // We have a mime type passed in so try to use it + switch (contentParameters.mimeType) { + case CONTENT_DASH: + mediaSourceType = C.TYPE_DASH; + break; + + case CONTENT_HLS: + mediaSourceType = C.TYPE_HLS; + break; + + case CONTENT_MP4: + case CONTENT_M4A: + case CONTENT_MKV: + case CONTENT_WEBM: + case CONTENT_OGG: + case CONTENT_MP3: + case CONTENT_AAC: + case CONTENT_TS: + case CONTENT_FLV: + case CONTENT_WAV: + mediaSourceType = C.TYPE_OTHER; + break; + + case CONTENT_SMOOTH_STREAMING: + mediaSourceType = C.TYPE_SS; + break; + + case CONTENT_TYPE_UNKNOWN: + default: + // no-op to try inferring from uri + break; + } + } + + // Parse the URI to try to determine the type. + // NOTE: inferContentType returns C.TYPE_OTHER if no matching signatures found + if (mediaSourceType == -1) { + mediaSourceType = Util.inferContentType(Uri.parse(contentParameters.url)); + } + + // Build the appropriate MediaSource + MediaSource mediaSource; + DataSource.Factory manifestDataSourceFactory = + new DefaultHttpDataSourceFactory(mUserAgent); + + switch (mediaSourceType) { + case C.TYPE_DASH: + mediaSource = new DashMediaSource.Factory( + new DefaultDashChunkSource.Factory(mDataSourceFactory), + buildDataSourceFactory(true)) + .createMediaSource(url, mMediaSourceHandler, eventLogger); + break; + + case C.TYPE_SS: + mediaSource = new SsMediaSource.Factory( + new DefaultSsChunkSource.Factory(mDataSourceFactory), + buildDataSourceFactory(true)) + .createMediaSource(url, mMediaSourceHandler, eventLogger); + break; + + case C.TYPE_HLS: + mediaSource = new HlsMediaSource.Factory(mDataSourceFactory) + .createMediaSource(url, mMediaSourceHandler, eventLogger); + break; + + case C.TYPE_OTHER: + default: + mediaSource = new ExtractorMediaSource.Factory(mDataSourceFactory) + .createMediaSource(url, mMediaSourceHandler, eventLogger); + } + + setPlayerState(PlayerState.OPENING); + if (mediaSource != null) { + mCurrentMediaSource = mediaSource; + setPlayerState(PlayerState.OPENED); + } + else { + setPlayerState(PlayerState.ERROR); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addCuesListener(OnCuesListener cuesListener) { + Log.v(TAG, "addCuesListener"); + if (!mCuesListeners.contains(cuesListener)) { + mCuesListeners.add(cuesListener); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeCuesListener(OnCuesListener cuesListener) { + mCuesListeners.remove(cuesListener); + } + + @Override + public void addId3MetadataListener(Id3MetadataListener id3MetadataListener) { + Log.w(TAG, "addId3MetadataListener not implemented"); + } + + @Override + public void removeId3MetadataListener(Id3MetadataListener id3MetadataListener) { + Log.w(TAG, "removeId3MetadataListener not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public void enableTextTrack(TrackType trackType, boolean b) { + Log.v(TAG, "enableTextTrack " + b); + int count = getTrackCount(trackType); + if (count > 0 && (trackType == TrackType.SUBTITLE || trackType == TrackType.CLOSED_CAPTION)) { + if (b) { + int rendererIndex = getRendererIndexByTrackType(trackType); + TrackGroupArray textGroups = mTrackSelector.getCurrentMappedTrackInfo().getTrackGroups(rendererIndex); + mTrackSelector.setRendererDisabled(rendererIndex, false); + MappingTrackSelector.SelectionOverride override = new MappingTrackSelector.SelectionOverride(mTrackSelectionFactory,0, 0); + mTrackSelector.setSelectionOverride(rendererIndex, textGroups, override); + mPlayer.addTextOutput(this); + } + else { + int rendererIndex = getRendererIndexByTrackType(trackType); + mTrackSelector.setRendererDisabled(rendererIndex, true); + mPlayer.removeTextOutput(this); + } + } + } + + + /** + * Helper to find the ExoPlayer TrackGroup by the TrackType + * @param trackType + * @return + */ + private int getRendererIndexByTrackType(TrackType trackType) { + boolean found = false; + for (int i = 0; i < mPlayer.getRendererCount(); i++) { + int type = mPlayer.getRendererType(i); + if (type == C.TRACK_TYPE_AUDIO && trackType == TrackType.AUDIO) { + return i; + } + else if (type == C.TRACK_TYPE_VIDEO && trackType == TrackType.VIDEO) { + return i; + } + else if (type == C.TRACK_TYPE_TEXT && (trackType == TrackType.CLOSED_CAPTION || trackType == TrackType.SUBTITLE)) { + return i; + } + else if (type == C.TRACK_TYPE_METADATA && trackType == TrackType.META_DATA) { + return i; + } + } + return -1; + } + /* + * {@inheritDoc} + */ + @Override + public int getTrackCount(TrackType trackType) { + Log.v(TAG, "getTrackCount"); + int trackCount = 0; + int rendererIndex = getRendererIndexByTrackType(trackType); + MappingTrackSelector.MappedTrackInfo mappedTrackInfo = mTrackSelector.getCurrentMappedTrackInfo(); + if (mappedTrackInfo != null) { + TrackGroupArray groups = mappedTrackInfo.getTrackGroups(rendererIndex); + if (groups != null) { + // most likely just one TrackGroup and one Format here + for (int i=0; i { + + /** + * Create the ExoPlayer2MediaPlayer instance, init needs to be called for usage. + * + * @return ExoPlayer2MediaPlayer instance. + */ + @Override + public UAMP createImpl() { + return new ExoPlayer2MediaPlayer(); + } +} diff --git a/ExoPlayer2MediaPlayerComponent/src/main/java/com/amazon/tv/Settings.java b/ExoPlayer2MediaPlayerComponent/src/main/java/com/amazon/tv/Settings.java new file mode 100644 index 0000000..2aaf98f --- /dev/null +++ b/ExoPlayer2MediaPlayerComponent/src/main/java/com/amazon/tv/Settings.java @@ -0,0 +1,34 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. 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. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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.amazon.tv; + +/** + * Constants required for limiting video quality. + * IMPORTANT NOTE: This file is copied from amazon_app_settings.jar from 'Amazon.com:FireOS 5 SDK + * (clean):22'. FireOS SDK uses SDK 22 but we need SDK 23 for compilation, hence cannot use FireOS + * SDK for compiling. + * These constants might change in future!!! Whenever FireOS is updated to use 23, replace this file + * with Settings class from FireOS. + */ +public final class Settings { + + public static final class Global { + + public static final String VIDEO_QUALITY = "com.amazon.tv.settings.VIDEO_QUALITY"; + public static final int VIDEO_QUALITY_GOOD = 0; + public static final int VIDEO_QUALITY_BETTER = 1; + public static final int VIDEO_QUALITY_BEST = 2; + } +} diff --git a/ExoPlayer2MediaPlayerComponent/src/main/res/values-en-rUS/strings.xml b/ExoPlayer2MediaPlayerComponent/src/main/res/values-en-rUS/strings.xml new file mode 100644 index 0000000..3c92709 --- /dev/null +++ b/ExoPlayer2MediaPlayerComponent/src/main/res/values-en-rUS/strings.xml @@ -0,0 +1,16 @@ + + + diff --git a/ExoPlayer2MediaPlayerComponent/src/main/res/values/strings.xml b/ExoPlayer2MediaPlayerComponent/src/main/res/values/strings.xml new file mode 100644 index 0000000..3c92709 --- /dev/null +++ b/ExoPlayer2MediaPlayerComponent/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + + From babc5b0f8a58090d9ed6592d76134d3faafc1efb Mon Sep 17 00:00:00 2001 From: Chris Thompson Date: Fri, 9 Aug 2019 09:08:09 -0400 Subject: [PATCH 3/4] Set Player to ExoPlayer2 --- Application/app/build.gradle | 2 +- Application/settings.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Application/app/build.gradle b/Application/app/build.gradle index edb4d04..36cdfee 100644 --- a/Application/app/build.gradle +++ b/Application/app/build.gradle @@ -96,7 +96,7 @@ dependencies { compile project(':TVUIComponent') compile project(':UAMP') - compile project(':AMZNMediaPlayerComponent') + compile project(':ExoPlayer2MediaPlayerComponent') compile project(':PassThroughAdsComponent') compile project(':PassThroughLoginComponent') compile project(':LoggerAnalyticsComponent') diff --git a/Application/settings.gradle b/Application/settings.gradle index 4912b06..df93554 100644 --- a/Application/settings.gradle +++ b/Application/settings.gradle @@ -33,7 +33,7 @@ include ':app', ':ModuleInterface', /* Implementations */ ':PassThroughAdsComponent', - ':AMZNMediaPlayerComponent', + ':ExoPlayer2MediaPlayerComponent', ':PassThroughLoginComponent', ':LoggerAnalyticsComponent' @@ -57,7 +57,7 @@ project(':AnalyticsInterface').projectDir = new File(rootProject.projectDir, '.. project(':PurchaseInterface').projectDir = new File(rootProject.projectDir, '../PurchaseInterface') /* Implementations */ -project(':AMZNMediaPlayerComponent').projectDir = new File(rootProject.projectDir, '../AMZNMediaPlayerComponent') +project(':ExoPlayer2MediaPlayerComponent').projectDir = new File(rootProject.projectDir, '../ExoPlayer2MediaPlayerComponent') project(':PassThroughAdsComponent').projectDir = new File(rootProject.projectDir, '../PassThroughAdsComponent') project(':PassThroughLoginComponent').projectDir = new File(rootProject.projectDir, '../PassThroughLoginComponent') project(':LoggerAnalyticsComponent').projectDir = new File(rootProject.projectDir, '../LoggerAnalyticsComponent') From 1e453b1f1c9a2e6639bbab0c3005d452b61757cd Mon Sep 17 00:00:00 2001 From: Christopher D Thompson Date: Wed, 19 Feb 2020 11:21:31 -0500 Subject: [PATCH 4/4] Update README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 606e08e..38b5e8a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +# Port of Exoplayer2 to Amazon Fire App Builder + +This is not full featured but a functional port of ExoPlayer2 to the [Fire App Builder](https://github.com/amzn/fire-app-builder) framework. Use this as a starting point if you are encountering bugs or missing features from the default ExoPlayer used in that framework. + +My use case for needing the port was rebuffering when encountering discontinuities in timestamps of live HLS streams. + +*Original README contents below* + +----- + ## Fire App Builder Fire App Builder is a java framework that allows developers to quickly build immersive, java based Android media applications for Fire TV, without writing code. Fire App Builder accomplishes this by using a plug and play java framework with easy configuration files. Developers simply specify the format of their media feed in a json file and add resources for logos and colors to create a rich media TV experience quickly. Fire App Builder supports multiple modules for Analytics, Authentication and Advertising that you can enable for your app.