diff --git a/MuxExoPlayer/build.gradle b/MuxExoPlayer/build.gradle index 3ee45536..6a57457e 100644 --- a/MuxExoPlayer/build.gradle +++ b/MuxExoPlayer/build.gradle @@ -5,8 +5,8 @@ android { defaultConfig { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion - versionCode 14 - versionName "2.4.3" + versionCode 15 + versionName "2.4.4" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/MuxExoPlayer/libs/MuxCore.jar b/MuxExoPlayer/libs/MuxCore.jar index 33625eb5..e7abbc13 100644 Binary files a/MuxExoPlayer/libs/MuxCore.jar and b/MuxExoPlayer/libs/MuxCore.jar differ diff --git a/MuxExoPlayer/libs/version-v6.1.0 b/MuxExoPlayer/libs/version-v6.1.0 deleted file mode 100644 index ed15bef9..00000000 --- a/MuxExoPlayer/libs/version-v6.1.0 +++ /dev/null @@ -1 +0,0 @@ -v6.1.0 diff --git a/MuxExoPlayer/libs/version-v6.2.0 b/MuxExoPlayer/libs/version-v6.2.0 new file mode 100644 index 00000000..2dc8e03b --- /dev/null +++ b/MuxExoPlayer/libs/version-v6.2.0 @@ -0,0 +1 @@ +v6.2.0 diff --git a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/AdsImaSDKListener.java b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/AdsImaSDKListener.java index 758a0312..19b5596a 100644 --- a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/AdsImaSDKListener.java +++ b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/AdsImaSDKListener.java @@ -16,106 +16,107 @@ import com.mux.stats.sdk.core.model.ViewData; public class AdsImaSDKListener implements AdErrorEvent.AdErrorListener, AdEvent.AdEventListener { - private MuxBaseExoPlayer exoPlayerListener; - private boolean sendPlayOnStarted = false; - public AdsImaSDKListener(MuxBaseExoPlayer listener) { - exoPlayerListener = listener; - } + private final MuxBaseExoPlayer exoPlayerListener; + private boolean sendPlayOnStarted = false; - @Override - public void onAdError(AdErrorEvent adErrorEvent) { - if (exoPlayerListener != null) { - PlaybackEvent event = new com.mux.stats.sdk.core.events.playback.AdErrorEvent(null); - setupAdViewData(event, null); - exoPlayerListener.dispatch(event); - } - } + public AdsImaSDKListener(MuxBaseExoPlayer listener) { + exoPlayerListener = listener; + } - private void setupAdViewData(PlaybackEvent event, Ad ad) { - ViewData viewData = new ViewData(); - if (exoPlayerListener.getCurrentPosition() == 0) { - if (ad != null) { - viewData.setViewPrerollAdId(ad.getAdId()); - viewData.setViewPrerollCreativeId(ad.getCreativeId()); - } - } - event.setViewData(viewData); + @Override + public void onAdError(AdErrorEvent adErrorEvent) { + if (exoPlayerListener != null) { + PlaybackEvent event = new com.mux.stats.sdk.core.events.playback.AdErrorEvent(null); + setupAdViewData(event, null); + exoPlayerListener.dispatch(event); } + } - @Override - public void onAdEvent(AdEvent adEvent) { - if (exoPlayerListener != null) { - PlaybackEvent event = null; - Ad ad = adEvent.getAd(); - switch (adEvent.getType()) { - // Cases sorted in calling sequence - case LOADED: - // There is nothing needed here, for now. It is unclear what this event - // actually correlates to with regards to VAST _AND_ VMAP responses. - break; - case CONTENT_PAUSE_REQUESTED: - // Send pause event if we are currently playing or preparing to play content - if (exoPlayerListener.getState() == MuxBaseExoPlayer.PlayerState.PLAY || - exoPlayerListener.getState() == MuxBaseExoPlayer.PlayerState.PLAYING) { - exoPlayerListener.pause(); - } - exoPlayerListener.setState(MuxBaseExoPlayer.PlayerState.PLAYING_ADS); - dispatchAdPlaybackEvent(new AdBreakStartEvent(null), ad); - dispatchAdPlaybackEvent(new AdPlayEvent(null), ad); - sendPlayOnStarted = false; - break; - case STARTED: - // On the first STARTED, do not send AdPlay, as it was handled in - // CONTENT_PAUSE_REQUESTED - if (sendPlayOnStarted) { - dispatchAdPlaybackEvent(new AdPlayEvent(null), ad); - } else { - sendPlayOnStarted = true; - } - dispatchAdPlaybackEvent(new AdPlayingEvent(null), ad); - break; - case FIRST_QUARTILE: - dispatchAdPlaybackEvent(new AdFirstQuartileEvent(null), ad); - break; - case MIDPOINT: - dispatchAdPlaybackEvent(new AdMidpointEvent(null), ad); - break; - case THIRD_QUARTILE: - dispatchAdPlaybackEvent(new AdThirdQuartileEvent(null), ad); - break; - case COMPLETED: - dispatchAdPlaybackEvent(new AdEndedEvent(null), ad); - break; - case CONTENT_RESUME_REQUESTED: - // End the ad break, and then toggle playback state to ensure that - // we get a play/playing after the ads. - dispatchAdPlaybackEvent(new AdBreakEndEvent(null), ad); - if (exoPlayerListener.player != null && exoPlayerListener.player.get() != null) { - exoPlayerListener.player.get().setPlayWhenReady(false); - exoPlayerListener.setState(MuxBaseExoPlayer.PlayerState.FINISHED_PLAYING_ADS); - exoPlayerListener.player.get().setPlayWhenReady(true); - } - break; - case PAUSED: - dispatchAdPlaybackEvent(new AdPauseEvent(null), ad); - break; - case RESUMED: - dispatchAdPlaybackEvent(new AdPlayEvent(null), ad); - dispatchAdPlaybackEvent(new AdPlayingEvent(null), ad); - break; - case ALL_ADS_COMPLETED: - // Nothing to do here, as this depends on VAST vs VMAP and is not - // consistent between the two. - break; - default: - return; - } - } + private void setupAdViewData(PlaybackEvent event, Ad ad) { + ViewData viewData = new ViewData(); + if (exoPlayerListener.getCurrentPosition() == 0) { + if (ad != null) { + viewData.setViewPrerollAdId(ad.getAdId()); + viewData.setViewPrerollCreativeId(ad.getCreativeId()); + } } + event.setViewData(viewData); + } - private void dispatchAdPlaybackEvent(PlaybackEvent event, Ad ad) { - setupAdViewData(event, ad); - exoPlayerListener.dispatch(event); + @Override + public void onAdEvent(AdEvent adEvent) { + if (exoPlayerListener != null) { + PlaybackEvent event = null; + Ad ad = adEvent.getAd(); + switch (adEvent.getType()) { + // Cases sorted in calling sequence + case LOADED: + // There is nothing needed here, for now. It is unclear what this event + // actually correlates to with regards to VAST _AND_ VMAP responses. + break; + case CONTENT_PAUSE_REQUESTED: + // Send pause event if we are currently playing or preparing to play content + if (exoPlayerListener.getState() == MuxBaseExoPlayer.PlayerState.PLAY || + exoPlayerListener.getState() == MuxBaseExoPlayer.PlayerState.PLAYING) { + exoPlayerListener.pause(); + } + exoPlayerListener.setState(MuxBaseExoPlayer.PlayerState.PLAYING_ADS); + dispatchAdPlaybackEvent(new AdBreakStartEvent(null), ad); + dispatchAdPlaybackEvent(new AdPlayEvent(null), ad); + sendPlayOnStarted = false; + break; + case STARTED: + // On the first STARTED, do not send AdPlay, as it was handled in + // CONTENT_PAUSE_REQUESTED + if (sendPlayOnStarted) { + dispatchAdPlaybackEvent(new AdPlayEvent(null), ad); + } else { + sendPlayOnStarted = true; + } + dispatchAdPlaybackEvent(new AdPlayingEvent(null), ad); + break; + case FIRST_QUARTILE: + dispatchAdPlaybackEvent(new AdFirstQuartileEvent(null), ad); + break; + case MIDPOINT: + dispatchAdPlaybackEvent(new AdMidpointEvent(null), ad); + break; + case THIRD_QUARTILE: + dispatchAdPlaybackEvent(new AdThirdQuartileEvent(null), ad); + break; + case COMPLETED: + dispatchAdPlaybackEvent(new AdEndedEvent(null), ad); + break; + case CONTENT_RESUME_REQUESTED: + // End the ad break, and then toggle playback state to ensure that + // we get a play/playing after the ads. + dispatchAdPlaybackEvent(new AdBreakEndEvent(null), ad); + if (exoPlayerListener.player != null && exoPlayerListener.player.get() != null) { + exoPlayerListener.player.get().setPlayWhenReady(false); + exoPlayerListener.setState(MuxBaseExoPlayer.PlayerState.FINISHED_PLAYING_ADS); + exoPlayerListener.player.get().setPlayWhenReady(true); + } + break; + case PAUSED: + dispatchAdPlaybackEvent(new AdPauseEvent(null), ad); + break; + case RESUMED: + dispatchAdPlaybackEvent(new AdPlayEvent(null), ad); + dispatchAdPlaybackEvent(new AdPlayingEvent(null), ad); + break; + case ALL_ADS_COMPLETED: + // Nothing to do here, as this depends on VAST vs VMAP and is not + // consistent between the two. + break; + default: + return; + } } + } + + private void dispatchAdPlaybackEvent(PlaybackEvent event, Ad ad) { + setupAdViewData(event, ad); + exoPlayerListener.dispatch(event); + } } diff --git a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/MuxBaseExoPlayer.java b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/MuxBaseExoPlayer.java index c50156d1..4dbb7a9d 100644 --- a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/MuxBaseExoPlayer.java +++ b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/MuxBaseExoPlayer.java @@ -1,5 +1,7 @@ package com.mux.stats.sdk.muxstats; +import static android.os.SystemClock.elapsedRealtime; + import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; @@ -12,13 +14,10 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; - import androidx.annotation.Nullable; - import com.google.ads.interactivemedia.v3.api.AdsLoader; import com.google.ads.interactivemedia.v3.api.AdsManager; import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent; @@ -51,7 +50,6 @@ import com.mux.stats.sdk.core.model.CustomerVideoData; import com.mux.stats.sdk.core.model.CustomerViewData; import com.mux.stats.sdk.core.util.MuxLogger; - import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -63,908 +61,928 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; -import static android.os.SystemClock.elapsedRealtime; - public class MuxBaseExoPlayer extends EventBus implements IPlayerListener { - protected static final String TAG = "MuxStatsListener"; - // Error codes start at -1 as ExoPlaybackException codes start at 0 and go up. - protected static final int ERROR_UNKNOWN = -1; - protected static final int ERROR_DRM = -2; - protected static final int ERROR_IO = -3; - - protected static final int NUMBER_OF_FRAMES_THAT_ARE_CONSIDERED_PLAYBACK = 2; - - protected String mimeType; - protected Integer sourceWidth; - protected Integer sourceHeight; - protected Integer sourceAdvertisedBitrate; - protected Float sourceAdvertisedFramerate; - protected Long sourceDuration; - protected ExoPlayerHandler playerHandler; - protected FrameRenderedListener frameRenderedListener; - protected Timer updatePlayheadPositionTimer; - - protected WeakReference player; - protected WeakReference playerView; - protected WeakReference contextRef; - protected AdsImaSDKListener adsImaSdkListener; - - protected int streamType = -1; - - public enum PlayerState { - BUFFERING, REBUFFERING, SEEKING, SEEKED, ERROR, PAUSED, PLAY, PLAYING, PLAYING_ADS, - FINISHED_PLAYING_ADS, INIT, ENDED - } - protected PlayerState state; - protected MuxStats muxStats; - boolean seekingInProgress; - int numberOfFramesRenderedSinceSeekingStarted; - boolean playItemHaveVideoTrack; - - - MuxBaseExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, CustomerVideoData customerVideoData, - CustomerViewData customerViewData, boolean sentryEnabled, - INetworkRequest networkRequest) { - super(); - this.player = new WeakReference<>(player); - this.contextRef = new WeakReference<>(ctx); - state = PlayerState.INIT; - MuxStats.setHostDevice(new MuxDevice(ctx)); - MuxStats.setHostNetworkApi(networkRequest); - muxStats = new MuxStats(this, playerName, customerPlayerData, customerVideoData, customerViewData, sentryEnabled); - addListener(muxStats); - playerHandler = new ExoPlayerHandler(player.getApplicationLooper(), this ); - frameRenderedListener = new FrameRenderedListener(playerHandler); - playItemHaveVideoTrack = false; - setPlaybackHeadUpdateInterval(); - try { - adsImaSdkListener = new AdsImaSDKListener(this); - } catch (NoClassDefFoundError Err) { - // The ad modules are not included here, so we silently swallow the - // exception as the application can't be running ads anyway. - } - } - /** - * Get the instance of the IMA SDK Listener for tracking ads running through Google's - * IMA SDK within your application. - * - * @deprecated - * This method is no longer the preferred method to track Ad performance with - * Google's IMA SDK. - *

Use {@link MuxBaseExoPlayer#monitorImaAdsLoader(AdsLoader)} instead. - * @return the IMA SDK Listener - * @throws - */ - @Deprecated - public AdsImaSDKListener getIMASdkListener() { - try { - // Let's just check one of them - Class.forName("com.google.ads.interactivemedia.v3.api.Ad"); - Class.forName("com.google.ads.interactivemedia.v3.api.AdErrorEvent"); - Class.forName("com.google.ads.interactivemedia.v3.api.AdEvent"); - return new AdsImaSDKListener(this); - } catch (ClassNotFoundException cnfe) { - throw new IllegalStateException("IMA SDK Modules not found"); - } + protected static final String TAG = "MuxStatsListener"; + // Error codes start at -1 as ExoPlaybackException codes start at 0 and go up. + protected static final int ERROR_UNKNOWN = -1; + protected static final int ERROR_DRM = -2; + protected static final int ERROR_IO = -3; + + protected static final int NUMBER_OF_FRAMES_THAT_ARE_CONSIDERED_PLAYBACK = 2; + + protected String mimeType; + protected Integer sourceWidth; + protected Integer sourceHeight; + protected Integer sourceAdvertisedBitrate; + protected Float sourceAdvertisedFramerate; + protected Long sourceDuration; + protected ExoPlayerHandler playerHandler; + protected FrameRenderedListener frameRenderedListener; + protected Timer updatePlayheadPositionTimer; + + protected WeakReference player; + protected WeakReference playerView; + protected WeakReference contextRef; + protected AdsImaSDKListener adsImaSdkListener; + + protected int streamType = -1; + + public enum PlayerState { + BUFFERING, REBUFFERING, SEEKING, SEEKED, ERROR, PAUSED, PLAY, PLAYING, PLAYING_ADS, + FINISHED_PLAYING_ADS, INIT, ENDED + } + + protected PlayerState state; + protected MuxStats muxStats; + boolean seekingInProgress; + int numberOfFramesRenderedSinceSeekingStarted; + boolean playItemHaveVideoTrack; + + + MuxBaseExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, CustomerVideoData customerVideoData, + CustomerViewData customerViewData, boolean sentryEnabled, + INetworkRequest networkRequest) { + super(); + this.player = new WeakReference<>(player); + this.contextRef = new WeakReference<>(ctx); + state = PlayerState.INIT; + MuxStats.setHostDevice(new MuxDevice(ctx)); + MuxStats.setHostNetworkApi(networkRequest); + muxStats = new MuxStats(this, playerName, customerPlayerData, customerVideoData, + customerViewData, sentryEnabled); + addListener(muxStats); + playerHandler = new ExoPlayerHandler(player.getApplicationLooper(), this); + frameRenderedListener = new FrameRenderedListener(playerHandler); + playItemHaveVideoTrack = false; + setPlaybackHeadUpdateInterval(); + try { + adsImaSdkListener = new AdsImaSDKListener(this); + } catch (NoClassDefFoundError Err) { + // The ad modules are not included here, so we silently swallow the + // exception as the application can't be running ads anyway. } + } + + /** + * Get the instance of the IMA SDK Listener for tracking ads running through Google's IMA SDK + * within your application. + * + * @return the IMA SDK Listener + * @throws + * @deprecated This method is no longer the preferred method to track Ad performance with Google's + * IMA SDK. + *

Use {@link MuxBaseExoPlayer#monitorImaAdsLoader(AdsLoader)} instead. + */ + @Deprecated + public AdsImaSDKListener getIMASdkListener() { + try { + // Let's just check one of them + Class.forName("com.google.ads.interactivemedia.v3.api.Ad"); + Class.forName("com.google.ads.interactivemedia.v3.api.AdErrorEvent"); + Class.forName("com.google.ads.interactivemedia.v3.api.AdEvent"); + return new AdsImaSDKListener(this); + } catch (ClassNotFoundException cnfe) { + throw new IllegalStateException("IMA SDK Modules not found"); + } + } + + /** + * Monitor an instance of Google IMA SDK's AdsLoader + * + * @param adsLoader For ExoPlayer 2.12 AdsLoader is initialized only when the add is requested, + * this makes this method impossible to use. + */ + @SuppressWarnings("unused") + public void monitorImaAdsLoader(AdsLoader adsLoader) { + if (adsLoader == null) { + Log.e(TAG, "Null AdsLoader provided to monitorImaAdsLoader"); + return; + } + try { + // TODO: these may not be necessary, but doing it for the sake of it + Class.forName("com.google.ads.interactivemedia.v3.api.AdsLoader"); + Class.forName("com.google.ads.interactivemedia.v3.api.AdsManager"); + Class.forName("com.google.ads.interactivemedia.v3.api.AdErrorEvent"); + Class.forName("com.google.ads.interactivemedia.v3.api.AdEvent"); + Class.forName("com.google.ads.interactivemedia.v3.api.Ad"); + final MuxBaseExoPlayer baseExoPlayer = this; + adsLoader.addAdsLoadedListener(new AdsLoader.AdsLoadedListener() { + @Override + public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) { + // TODO: Add in the adresponse stuff when we can - /** - * Monitor an instance of Google IMA SDK's AdsLoader - * @param adsLoader - * - * - * For ExoPlayer 2.12 AdsLoader is initialized only when the add is requested, this makes - * this method impossible to use. - */ - @SuppressWarnings("unused") - public void monitorImaAdsLoader(AdsLoader adsLoader) { - if (adsLoader == null) { - Log.e(TAG, "Null AdsLoader provided to monitorImaAdsLoader"); - return; - } - try { - // TODO: these may not be necessary, but doing it for the sake of it - Class.forName("com.google.ads.interactivemedia.v3.api.AdsLoader"); - Class.forName("com.google.ads.interactivemedia.v3.api.AdsManager"); - Class.forName("com.google.ads.interactivemedia.v3.api.AdErrorEvent"); - Class.forName("com.google.ads.interactivemedia.v3.api.AdEvent"); - Class.forName("com.google.ads.interactivemedia.v3.api.Ad"); - final MuxBaseExoPlayer baseExoPlayer = this; - adsLoader.addAdsLoadedListener(new AdsLoader.AdsLoadedListener() { - @Override - public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) { - // TODO: Add in the adresponse stuff when we can - - // Set up the ad events that we want to use - AdsManager adsManager = adsManagerLoadedEvent.getAdsManager(); - - // Attach mux event and error event listeners. - adsManager.addAdErrorListener(adsImaSdkListener); - adsManager.addAdEventListener(adsImaSdkListener); - } - - // TODO: probably need to handle some cleanup and things, like removing listeners on destroy - }); - } catch (ClassNotFoundException cnfe) { - return; + // Set up the ad events that we want to use + AdsManager adsManager = adsManagerLoadedEvent.getAdsManager(); + + // Attach mux event and error event listeners. + adsManager.addAdErrorListener(adsImaSdkListener); + adsManager.addAdEventListener(adsImaSdkListener); } - } - // ExoPlayer 2.12+ need this to hook add events - public AdsImaSDKListener getAdsImaSdkListener() { - return adsImaSdkListener; + // TODO: probably need to handle some cleanup and things, like removing listeners on destroy + }); + } catch (ClassNotFoundException cnfe) { + return; } - - @Deprecated - public AdsImaSDKListener getAdErrorEventListener() { - return adsImaSdkListener; + } + + // ExoPlayer 2.12+ need this to hook add events + public AdsImaSDKListener getAdsImaSdkListener() { + return adsImaSdkListener; + } + + @Deprecated + public AdsImaSDKListener getAdErrorEventListener() { + return adsImaSdkListener; + } + + @Deprecated + public AdsImaSDKListener getAdEventListener() { + return adsImaSdkListener; + } + + @SuppressWarnings("unused") + public void updateCustomerData(CustomerPlayerData customPlayerData, + CustomerVideoData customVideoData) { + muxStats.updateCustomerData(customPlayerData, customVideoData); + } + + @SuppressWarnings("unused") + public void updateCustomerData(CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData) { + muxStats.updateCustomerData(customerPlayerData, customerVideoData, customerViewData); + } + + @SuppressWarnings("unused") + public CustomerVideoData getCustomerVideoData() { + return muxStats.getCustomerVideoData(); + } + + @SuppressWarnings("unused") + public CustomerPlayerData getCustomerPlayerData() { + return muxStats.getCustomerPlayerData(); + } + + @SuppressWarnings("unused") + public CustomerViewData getCustomerViewData() { + return muxStats.getCustomerViewData(); + } + + public void enableMuxCoreDebug(boolean enable, boolean verbose) { + muxStats.allowLogcatOutput(enable, verbose); + } + + @SuppressWarnings("unused") + public void videoChange(CustomerVideoData customerVideoData) { + muxStats.videoChange(customerVideoData); + } + + @SuppressWarnings("unused") + public void programChange(CustomerVideoData customerVideoData) { + muxStats.programChange(customerVideoData); + } + + public void orientationChange(MuxSDKViewOrientation orientation) { + muxStats.orientationChange(orientation); + } + + public void setPlayerView(View playerView) { + this.playerView = new WeakReference<>(playerView); + } + + @SuppressWarnings("unused") + public void setPlayerSize(int width, int height) { + muxStats.setPlayerSize(width, height); + } + + public void setScreenSize(int width, int height) { + muxStats.setScreenSize(width, height); + } + + public void error(MuxErrorException e) { + muxStats.error(e); + } + + @SuppressWarnings("unused") + public void setAutomaticErrorTracking(boolean enabled) { + muxStats.setAutomaticErrorTracking(enabled); + } + + public void release() { + muxStats.release(); + muxStats = null; + player = null; + } + + @SuppressWarnings("unused") + public void setStreamType(int type) { + streamType = type; + } + + @Override + public void dispatch(IEvent event) { + if (player != null && player.get() != null && muxStats != null) { + super.dispatch(event); } + } - @Deprecated - public AdsImaSDKListener getAdEventListener() { - return adsImaSdkListener; + @Override + public long getCurrentPosition() { + if (playerHandler != null) { + return playerHandler.getPlayerCurrentPosition(); } - - @SuppressWarnings("unused") - public void updateCustomerData(CustomerPlayerData customPlayerData, CustomerVideoData customVideoData) { - muxStats.updateCustomerData(customPlayerData, customVideoData); + return 0; + } + + @Override + public String getMimeType() { + return mimeType; + } + + @Override + public Integer getSourceWidth() { + return sourceWidth; + } + + @Override + public Integer getSourceHeight() { + return sourceHeight; + } + + @Override + public Integer getSourceAdvertisedBitrate() { + return sourceAdvertisedBitrate; + } + + @Override + public Float getSourceAdvertisedFramerate() { + return sourceAdvertisedFramerate; + } + + @Override + public Long getSourceDuration() { + return sourceDuration; + } + + // State Transitions + public PlayerState getState() { + return state; + } + + protected void configurePlaybackHeadUpdateInterval() { + if (player == null || player.get() == null) { + return; } - @SuppressWarnings("unused") - public void updateCustomerData(CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData) { - muxStats.updateCustomerData(customerPlayerData, customerVideoData, customerViewData); + TrackGroupArray trackGroups = player.get().getCurrentTrackGroups(); + playItemHaveVideoTrack = false; + if (trackGroups.length > 0) { + for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { + TrackGroup trackGroup = trackGroups.get(groupIndex); + if (0 < trackGroup.length) { + Format trackFormat = trackGroup.getFormat(0); + if (trackFormat.sampleMimeType != null && trackFormat.sampleMimeType.contains("video")) { + playItemHaveVideoTrack = true; + break; + } + } + } } + setPlaybackHeadUpdateInterval(); + } - @SuppressWarnings("unused") - public CustomerVideoData getCustomerVideoData() { - return muxStats.getCustomerVideoData(); + protected void setPlaybackHeadUpdateInterval() { + if (updatePlayheadPositionTimer != null) { + updatePlayheadPositionTimer.cancel(); } - - @SuppressWarnings("unused") - public CustomerPlayerData getCustomerPlayerData() { - return muxStats.getCustomerPlayerData(); + if (playItemHaveVideoTrack) { + Player.VideoComponent videoComponent = player.get().getVideoComponent(); + videoComponent.setVideoFrameMetadataListener(frameRenderedListener); + } else { + // Schedule timer to execute, this is for audio only content. + updatePlayheadPositionTimer = new Timer(); + updatePlayheadPositionTimer.schedule(new TimerTask() { + @Override + public void run() { + playerHandler.obtainMessage(ExoPlayerHandler.UPDATE_PLAYER_CURRENT_POSITION) + .sendToTarget(); + } + }, 0, 15); } - - @SuppressWarnings("unused") - public CustomerViewData getCustomerViewData() { - return muxStats.getCustomerViewData(); + } + + /* + * This will be called by AdsImaSDKListener to set the player state to: PLAYING_ADS + * and ADS_PLAYBACK_DONE accordingly + */ + protected void setState(PlayerState newState) { + state = newState; + } + + @Override + public boolean isBuffering() { + return getState() == MuxBaseExoPlayer.PlayerState.BUFFERING; + } + + @Override + public int getPlayerViewWidth() { + if (this.playerView != null) { + View pv = this.playerView.get(); + if (pv != null) { + return pxToDp(pv.getWidth()); + } } - - public void enableMuxCoreDebug(boolean enable, boolean verbose) { - muxStats.allowLogcatOutput(enable, verbose); + return 0; + } + + @Override + public int getPlayerViewHeight() { + if (this.playerView != null) { + View pv = this.playerView.get(); + if (pv != null) { + return pxToDp(pv.getHeight()); + } } - - @SuppressWarnings("unused") - public void videoChange(CustomerVideoData customerVideoData) { - muxStats.videoChange(customerVideoData); + return 0; + } + + @Override + public boolean isPaused() { + return state == PlayerState.PAUSED || state == PlayerState.ENDED || state == PlayerState.ERROR + || state == PlayerState.INIT; + } + + protected void buffering() { + if (state == PlayerState.REBUFFERING || seekingInProgress + || state == PlayerState.SEEKED) { + // ignore + return; } - - @SuppressWarnings("unused") - public void programChange(CustomerVideoData customerVideoData) { - muxStats.programChange(customerVideoData); + // If we are going from playing to buffering then this is rebuffer event + if (state == PlayerState.PLAYING) { + rebufferingStarted(); + return; } - - public void orientationChange(MuxSDKViewOrientation orientation) { - muxStats.orientationChange(orientation); + // This is initial buffering event before playback starts + state = PlayerState.BUFFERING; + dispatch(new TimeUpdateEvent(null)); + } + + protected void pause() { + if (state == PlayerState.SEEKED) { + // No pause event after seeked + return; } + if (state == PlayerState.REBUFFERING) { + rebufferingEnded(); + } + if (seekingInProgress) { + seeked(false); + return; + } + state = PlayerState.PAUSED; + dispatch(new PauseEvent(null)); + } + + protected void play() { + if (state == PlayerState.REBUFFERING + || seekingInProgress + || state == PlayerState.SEEKED) { + // Ignore play event after rebuffering and Seeking + return; + } + state = PlayerState.PLAY; + dispatch(new PlayEvent(null)); + } + + protected void playing() { + if (seekingInProgress) { + // We will dispatch playing event after seeked event + return; + } + if (state == PlayerState.PAUSED || state == PlayerState.FINISHED_PLAYING_ADS) { + play(); + } + if (state == PlayerState.REBUFFERING) { + rebufferingEnded(); + } + + state = PlayerState.PLAYING; + dispatch(new PlayingEvent(null)); + } + + protected void rebufferingStarted() { + state = PlayerState.REBUFFERING; + dispatch(new RebufferStartEvent(null)); + } - public void setPlayerView(View playerView) { - this.playerView = new WeakReference<>(playerView); + protected void rebufferingEnded() { + dispatch(new RebufferEndEvent(null)); + } + + protected void seeking() { + if (state == PlayerState.PLAYING) { + dispatch(new PauseEvent(null)); } + state = PlayerState.SEEKING; + seekingInProgress = true; + numberOfFramesRenderedSinceSeekingStarted = 0; + dispatch(new SeekingEvent(null)); + } - @SuppressWarnings("unused") - public void setPlayerSize(int width, int height) { - muxStats.setPlayerSize(width, height); + protected void seeked(boolean newFrameRendered) { + /* + * Seeked event will be fired by the player immediately after seeking event + * This is not accurate, instead report the seeked event on first few frames rendered. + * This function is called each time a new frame is about to be rendered. + */ + if (seekingInProgress) { + if (newFrameRendered) { + if (numberOfFramesRenderedSinceSeekingStarted + > NUMBER_OF_FRAMES_THAT_ARE_CONSIDERED_PLAYBACK) { + // This is a playback !!! + dispatch(new SeekedEvent(null)); + seekingInProgress = false; + playing(); + } else { + numberOfFramesRenderedSinceSeekingStarted++; + } + } else { + // the player was seeking while paused + dispatch(new SeekedEvent(null)); + seekingInProgress = false; + state = PlayerState.SEEKED; + } } + } + + protected void ended() { + dispatch(new PauseEvent(null)); + dispatch(new EndedEvent(null)); + state = PlayerState.ENDED; + } + + protected void internalError(Exception error) { + if (error instanceof MuxErrorException) { + MuxErrorException muxError = (MuxErrorException) error; + dispatch(new InternalErrorEvent(muxError.getCode(), muxError.getMessage())); + } else { + dispatch(new InternalErrorEvent(ERROR_UNKNOWN, + error.getClass().getCanonicalName() + " - " + error.getMessage())); + } + } + + protected void handleRenditionChange(Format format) { + if (format != null) { + sourceAdvertisedBitrate = format.bitrate; + if (format.frameRate > 0) { + sourceAdvertisedFramerate = format.frameRate; + } + sourceWidth = format.width; + sourceHeight = format.height; + RenditionChangeEvent event = new RenditionChangeEvent(null); + dispatch(event); + } + } + + static class FrameRenderedListener implements VideoFrameMetadataListener { - public void setScreenSize(int width, int height) { - muxStats.setScreenSize(width, height); + ExoPlayerHandler handler; + + public FrameRenderedListener(ExoPlayerHandler handler) { + this.handler = handler; } - public void error(MuxErrorException e) { - muxStats.error(e); + // As of r2.11.x, the signature for this callback has changed. These are not annotated as @Overrides in + // order to support both before r2.11.x and after r2.11.x at the same time. + public void onVideoFrameAboutToBeRendered(long presentationTimeUs, long releaseTimeNs, + Format format) { + handler.obtainMessage(ExoPlayerHandler.UPDATE_PLAYER_CURRENT_POSITION).sendToTarget(); } - @SuppressWarnings("unused") - public void setAutomaticErrorTracking(boolean enabled) { - muxStats.setAutomaticErrorTracking(enabled); + public void onVideoFrameAboutToBeRendered(long presentationTimeUs, long releaseTimeNs, + Format format, @Nullable MediaFormat mediaFormat) { + handler.obtainMessage(ExoPlayerHandler.UPDATE_PLAYER_CURRENT_POSITION).sendToTarget(); } + } + + static class ExoPlayerHandler extends Handler { + + static final int UPDATE_PLAYER_CURRENT_POSITION = 1; + + AtomicLong playerCurrentPosition = new AtomicLong(0); + MuxBaseExoPlayer muxStats; - public void release() { - muxStats.release(); - muxStats = null; - player = null; + public ExoPlayerHandler(Looper looper, MuxBaseExoPlayer muxStats) { + super(looper); + this.muxStats = muxStats; } - @SuppressWarnings("unused") - public void setStreamType(int type) { - streamType = type; + public long getPlayerCurrentPosition() { + return playerCurrentPosition.get(); } - @Override - public void dispatch(IEvent event) { - if (player != null && player.get() != null && muxStats != null) { - super.dispatch(event); - } + public void handleMessage(Message msg) { + switch (msg.what) { + case UPDATE_PLAYER_CURRENT_POSITION: + if (muxStats == null || muxStats.player == null) { + return; + } + if (muxStats.player.get() != null) { + playerCurrentPosition.set(muxStats.player.get().getContentPosition()); + } + muxStats.seeked(true); + break; + default: + Log.e(TAG, "ExoPlayerHandler>> Unhandled message type: " + msg.what); + } } + } - @Override - public long getCurrentPosition() { - if (playerHandler != null) - return playerHandler.getPlayerCurrentPosition(); - return 0; + static class MuxDevice implements IDevice { + + private static final String EXO_SOFTWARE = "ExoPlayer"; + + static final String CONNECTION_TYPE_CELLULAR = "cellular"; + static final String CONNECTION_TYPE_WIFI = "wifi"; + static final String CONNECTION_TYPE_WIRED = "wired"; + static final String CONNECTION_TYPE_OTHER = "other"; + + static final String MUX_DEVICE_ID = "MUX_DEVICE_ID"; + + protected WeakReference contextRef; + private String deviceId; + private String appName = ""; + private String appVersion = ""; + + MuxDevice(Context ctx) { + SharedPreferences sharedPreferences = ctx + .getSharedPreferences(MUX_DEVICE_ID, Context.MODE_PRIVATE); + deviceId = sharedPreferences.getString(MUX_DEVICE_ID, null); + if (deviceId == null) { + deviceId = UUID.randomUUID().toString(); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(MUX_DEVICE_ID, deviceId); + editor.commit(); + } + contextRef = new WeakReference<>(ctx); + try { + PackageInfo pi = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); + appName = pi.packageName; + appVersion = pi.versionName; + } catch (PackageManager.NameNotFoundException e) { + MuxLogger.d(TAG, "could not get package info"); + } } @Override - public String getMimeType() { - return mimeType; + public String getHardwareArchitecture() { + return Build.HARDWARE; } @Override - public Integer getSourceWidth() { - return sourceWidth; + public String getOSFamily() { + return "Android"; } @Override - public Integer getSourceHeight() { - return sourceHeight; + public String getOSVersion() { + return Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")"; } @Override - public Integer getSourceAdvertisedBitrate() { - return sourceAdvertisedBitrate; + public String getManufacturer() { + return Build.MANUFACTURER; } @Override - public Float getSourceAdvertisedFramerate() { - return sourceAdvertisedFramerate; + public String getModelName() { + return Build.MODEL; } @Override - public Long getSourceDuration() { - return sourceDuration; + public String getPlayerVersion() { + return ExoPlayerLibraryInfo.VERSION; } - // State Transitions - public PlayerState getState() { - return state; + @Override + public String getDeviceId() { + return deviceId; } - protected void configurePlaybackHeadUpdateInterval() { - if (player == null || player.get() == null) { - return; - } - - TrackGroupArray trackGroups = player.get().getCurrentTrackGroups(); - playItemHaveVideoTrack = false; - if (trackGroups.length > 0) { - for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { - TrackGroup trackGroup = trackGroups.get(groupIndex); - if (0 < trackGroup.length) { - Format trackFormat = trackGroup.getFormat(0); - if (trackFormat.sampleMimeType != null && trackFormat.sampleMimeType.contains("video")) { - playItemHaveVideoTrack = true; - break; - } - } - } - } - setPlaybackHeadUpdateInterval(); + @Override + public String getAppName() { + return appName; } - protected void setPlaybackHeadUpdateInterval() { - if (updatePlayheadPositionTimer != null) { - updatePlayheadPositionTimer.cancel(); - } - if (playItemHaveVideoTrack) { - Player.VideoComponent videoComponent = player.get().getVideoComponent(); - videoComponent.setVideoFrameMetadataListener(frameRenderedListener); - } else { - // Schedule timer to execute, this is for audio only content. - updatePlayheadPositionTimer = new Timer(); - updatePlayheadPositionTimer.schedule(new TimerTask() { - @Override - public void run() { - playerHandler.obtainMessage(ExoPlayerHandler.UPDATE_PLAYER_CURRENT_POSITION) - .sendToTarget(); - } - }, 0, 15); - } + @Override + public String getAppVersion() { + return appVersion; } - /* - * This will be called by AdsImaSDKListener to set the player state to: PLAYING_ADS - * and ADS_PLAYBACK_DONE accordingly - */ - protected void setState(PlayerState newState) { - state = newState; + @Override + public String getPluginName() { + return BuildConfig.MUX_PLUGIN_NAME; } @Override - public boolean isBuffering() { - return getState() == MuxBaseExoPlayer.PlayerState.BUFFERING; + public String getPluginVersion() { + return BuildConfig.MUX_PLUGIN_VERSION; } @Override - public int getPlayerViewWidth() { - if (this.playerView != null) { - View pv = this.playerView.get(); - if (pv != null) { - return pxToDp(pv.getWidth()); - } - } - return 0; + public String getPlayerSoftware() { + return EXO_SOFTWARE; } @Override - public int getPlayerViewHeight() { - if (this.playerView != null) { - View pv = this.playerView.get(); - if (pv != null) { - return pxToDp(pv.getHeight()); - } - } - return 0; + public String getNetworkConnectionType() { + // Checking internet connectivity + Context context = contextRef.get(); + if (context == null) { + return null; + } + ConnectivityManager connectivityMgr = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = null; + if (connectivityMgr != null) { + activeNetwork = connectivityMgr.getActiveNetworkInfo(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + NetworkCapabilities nc = connectivityMgr + .getNetworkCapabilities(connectivityMgr.getActiveNetwork()); + if (nc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + return CONNECTION_TYPE_WIRED; + } else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return CONNECTION_TYPE_WIFI; + } else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return CONNECTION_TYPE_CELLULAR; + } else { + return CONNECTION_TYPE_OTHER; + } + } else { + if (activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET) { + return CONNECTION_TYPE_WIRED; + } else if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { + return CONNECTION_TYPE_WIFI; + } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { + return CONNECTION_TYPE_CELLULAR; + } else { + return CONNECTION_TYPE_OTHER; + } + } + } + return null; } @Override - public boolean isPaused() { - return state == PlayerState.PAUSED || state == PlayerState.ENDED || state == PlayerState.ERROR || state == PlayerState.INIT; + public long getElapsedRealtime() { + return elapsedRealtime(); } - protected void buffering() { - if (state == PlayerState.REBUFFERING || seekingInProgress - || state == PlayerState.SEEKED ) { - // ignore - return; - } - // If we are going from playing to buffering then this is rebuffer event - if (state == PlayerState.PLAYING) { - rebufferingStarted(); - return; - } - // This is initial buffering event before playback starts - state = PlayerState.BUFFERING; - dispatch(new TimeUpdateEvent(null)); + @Override + public void outputLog(String tag, String msg) { + Log.v(tag, msg); } - - protected void pause() { - if ( state == PlayerState.SEEKED ) { - // No pause event after seeked - return; - } - if (state == PlayerState.REBUFFERING) { - rebufferingEnded(); - } - if ( seekingInProgress ) { - seeked( false ); - return; - } - state = PlayerState.PAUSED; - dispatch(new PauseEvent(null)); + } + + class BandwidthMetric { + + public BandwidthMetricData onLoadError(DataSpec dataSpec, int dataType, IOException e) { + BandwidthMetricData loadData = new BandwidthMetricData(); + loadData.setRequestError(e.toString()); + if (dataSpec != null && dataSpec.uri != null) { + loadData.setRequestUrl(dataSpec.uri.toString()); + loadData.setRequestHostName(dataSpec.uri.getHost()); + } + switch (dataType) { + case C.DATA_TYPE_MANIFEST: + loadData.setRequestType("manifest"); + break; + case C.DATA_TYPE_MEDIA: + loadData.setRequestType("media"); + break; + default: + return null; + } + loadData.setRequestErrorCode(null); + loadData.setRequestErrorText(e.getMessage()); + return loadData; } - protected void play() { - if (state == PlayerState.REBUFFERING - || seekingInProgress - || state == PlayerState.SEEKED ) { - // Ignore play event after rebuffering and Seeking - return; - } - state = PlayerState.PLAY; - dispatch(new PlayEvent(null)); + public BandwidthMetricData onLoadCanceled(DataSpec dataSpec) { + BandwidthMetricData loadData = new BandwidthMetricData(); + loadData.setRequestCancel("genericLoadCanceled"); + if (dataSpec != null && dataSpec.uri != null) { + loadData.setRequestUrl(dataSpec.uri.toString()); + loadData.setRequestHostName(dataSpec.uri.getHost()); + } + loadData.setRequestType("media"); + return loadData; } - protected void playing() { - if ( seekingInProgress ) { - // We will dispatch playing event after seeked event - return; - } - if (state == PlayerState.PAUSED || state == PlayerState.FINISHED_PLAYING_ADS) { - play(); - } - if (state == PlayerState.REBUFFERING) { - rebufferingEnded(); - } - - state = PlayerState.PLAYING; - dispatch(new PlayingEvent(null)); + protected BandwidthMetricData onLoad(DataSpec dataSpec, int dataType, + Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, + long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) { + BandwidthMetricData loadData = new BandwidthMetricData(); + if (bytesLoaded > 0) { + loadData.setRequestBytesLoaded(bytesLoaded); + } + switch (dataType) { + case C.DATA_TYPE_MANIFEST: + loadData.setRequestType("manifest"); + break; + case C.DATA_TYPE_MEDIA: + loadData.setRequestType("media"); + break; + default: + return null; + } + loadData.setRequestResponseHeaders(null); + if (dataSpec != null && dataSpec.uri != null) { + loadData.setRequestHostName(dataSpec.uri.getHost()); + } + if (dataType == C.DATA_TYPE_MEDIA) { + loadData.setRequestMediaDuration(mediaEndTimeMs - mediaStartTimeMs); + } + if (trackFormat != null) { + loadData.setRequestCurrentLevel(null); + if (dataType == C.DATA_TYPE_MEDIA) { + loadData.setRequestMediaStartTime(mediaStartTimeMs); + } + loadData.setRequestVideoWidth(trackFormat.width); + loadData.setRequestVideoHeight(trackFormat.height); + } + loadData.setRequestRenditionLists(renditionList); + return loadData; } - protected void rebufferingStarted() { - state = PlayerState.REBUFFERING; - dispatch(new RebufferStartEvent(null)); + public BandwidthMetricData onLoadStarted(DataSpec dataSpec, int dataType, + Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) { + BandwidthMetricData loadData = onLoad(dataSpec, dataType, trackFormat, + mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs, 0, 0); + if (loadData != null) { + loadData.setRequestResponseStart(elapsedRealtimeMs); + } + return loadData; } - protected void rebufferingEnded() { - dispatch(new RebufferEndEvent(null)); + public BandwidthMetricData onLoadCompleted(DataSpec dataSpec, int dataType, + Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, + long loadDurationMs, long bytesLoaded) { + BandwidthMetricData loadData = onLoad(dataSpec, dataType, trackFormat, + mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs, bytesLoaded); + if (loadData != null) { + loadData.setRequestResponseStart(elapsedRealtimeMs - loadDurationMs); + loadData.setRequestResponseEnd(elapsedRealtimeMs); + } + return loadData; } + } - protected void seeking() { - if (state == PlayerState.PLAYING) { - dispatch(new PauseEvent(null)); - } - state = PlayerState.SEEKING; - seekingInProgress = true; - numberOfFramesRenderedSinceSeekingStarted = 0; - dispatch(new SeekingEvent(null)); - } - - protected void seeked( boolean newFrameRendered ) { - /* - * Seeked event will be fired by the player immediately after seeking event - * This is not accurate, instead report the seeked event on first few frames rendered. - * This function is called each time a new frame is about to be rendered. - */ - if ( seekingInProgress ) { - if ( newFrameRendered ) { - if ( numberOfFramesRenderedSinceSeekingStarted > NUMBER_OF_FRAMES_THAT_ARE_CONSIDERED_PLAYBACK ) { - // This is a playback !!! - dispatch(new SeekedEvent(null)); - seekingInProgress = false; - playing(); - } else { - numberOfFramesRenderedSinceSeekingStarted++; - } - } else { - // the player was seeking while paused - dispatch(new SeekedEvent(null)); - seekingInProgress = false; - state = PlayerState.SEEKED; - } - } - } + class BandwidthMetricHls extends BandwidthMetric { - protected void ended() { - dispatch(new PauseEvent(null)); - dispatch(new EndedEvent(null)); - state = PlayerState.ENDED; + @Override + public BandwidthMetricData onLoadError(DataSpec dataSpec, int dataType, IOException e) { + BandwidthMetricData loadData = super.onLoadError(dataSpec, C.DATA_TYPE_MEDIA, e); + return loadData; } - protected void internalError(Exception error) { - if (error instanceof MuxErrorException) { - MuxErrorException muxError = (MuxErrorException) error; - dispatch(new InternalErrorEvent(muxError.getCode(), muxError.getMessage())); - } else { - dispatch(new InternalErrorEvent(ERROR_UNKNOWN, error.getClass().getCanonicalName() + " - " + error.getMessage())); - } + @Override + public BandwidthMetricData onLoadCanceled(DataSpec dataSpec) { + BandwidthMetricData loadData = super.onLoadCanceled(dataSpec); + loadData.setRequestCancel("hlsFragLoadEmergencyAborted"); + return loadData; } - protected void handleRenditionChange(Format format) { - if (format != null) { - sourceAdvertisedBitrate = format.bitrate; - if (format.frameRate > 0) { - sourceAdvertisedFramerate = format.frameRate; - } - sourceWidth = format.width; - sourceHeight = format.height; - RenditionChangeEvent event = new RenditionChangeEvent(null); - dispatch(event); - } + @Override + public BandwidthMetricData onLoadCompleted(DataSpec dataSpec, int dataType, + Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, + long loadDurationMs, long bytesLoaded) { + BandwidthMetricData loadData = super.onLoadCompleted(dataSpec, dataType, trackFormat, + mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs, bytesLoaded); + if (loadData != null) { + switch (dataType) { + case C.DATA_TYPE_MANIFEST: + loadData.setRequestEventType("hlsManifestLoaded"); + break; + case C.DATA_TYPE_MEDIA: + loadData.setRequestEventType("hlsFragBuffered"); + break; + default: + break; + } + if (trackFormat != null) { + loadData.setRequestLabeledBitrate(trackFormat.bitrate); + } + } + return loadData; } + } - static class FrameRenderedListener implements VideoFrameMetadataListener { - ExoPlayerHandler handler; - - public FrameRenderedListener(ExoPlayerHandler handler) { - this.handler = handler; - } - - // As of r2.11.x, the signature for this callback has changed. These are not annotated as @Overrides in - // order to support both before r2.11.x and after r2.11.x at the same time. - public void onVideoFrameAboutToBeRendered(long presentationTimeUs, long releaseTimeNs, Format format) { - handler.obtainMessage(ExoPlayerHandler.UPDATE_PLAYER_CURRENT_POSITION).sendToTarget(); - } - - public void onVideoFrameAboutToBeRendered(long presentationTimeUs, long releaseTimeNs, Format format, @Nullable MediaFormat mediaFormat) { - handler.obtainMessage(ExoPlayerHandler.UPDATE_PLAYER_CURRENT_POSITION).sendToTarget(); - } - }; - - static class ExoPlayerHandler extends Handler { - static final int UPDATE_PLAYER_CURRENT_POSITION = 1; + class BandwidthMetricDash extends BandwidthMetric { - AtomicLong playerCurrentPosition = new AtomicLong(0); - MuxBaseExoPlayer muxStats; - - public ExoPlayerHandler(Looper looper, MuxBaseExoPlayer muxStats) { - super(looper); - this.muxStats = muxStats; - } - - public long getPlayerCurrentPosition() { - return playerCurrentPosition.get(); - } - - public void handleMessage(Message msg) { - switch (msg.what) { - case UPDATE_PLAYER_CURRENT_POSITION: - if (muxStats == null || muxStats.player == null) { - return; - } - if (muxStats.player.get() != null) { - playerCurrentPosition.set(muxStats.player.get().getContentPosition()); - } - muxStats.seeked( true ); - break; - default: - Log.e(TAG, "ExoPlayerHandler>> Unhandled message type: " + msg.what); - } - } + @Override + public BandwidthMetricData onLoadStarted(DataSpec dataSpec, int dataType, + Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) { + BandwidthMetricData loadData = super.onLoadStarted(dataSpec, dataType, trackFormat, + mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs); + if (loadData != null) { + switch (dataType) { + case C.DATA_TYPE_MEDIA: + loadData.setRequestEventType("initFragmentLoaded"); + break; + default: + break; + } + } + return loadData; } - static class MuxDevice implements IDevice { - private static final String EXO_SOFTWARE = "ExoPlayer"; - - static final String CONNECTION_TYPE_CELLULAR = "cellular"; - static final String CONNECTION_TYPE_WIFI = "wifi"; - static final String CONNECTION_TYPE_WIRED = "wired"; - static final String CONNECTION_TYPE_OTHER = "other"; - - static final String MUX_DEVICE_ID = "MUX_DEVICE_ID"; - - protected WeakReference contextRef; - private String deviceId; - private String appName = ""; - private String appVersion = ""; - - MuxDevice(Context ctx) { - SharedPreferences sharedPreferences = ctx.getSharedPreferences(MUX_DEVICE_ID, Context.MODE_PRIVATE); - deviceId = sharedPreferences.getString(MUX_DEVICE_ID, null); - if (deviceId == null) { - deviceId = UUID.randomUUID().toString(); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(MUX_DEVICE_ID, deviceId); - editor.commit(); - } - contextRef = new WeakReference<>(ctx); - try { - PackageInfo pi = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); - appName = pi.packageName; - appVersion = pi.versionName; - } catch (PackageManager.NameNotFoundException e) { - MuxLogger.d(TAG, "could not get package info"); - } - } - - @Override - public String getHardwareArchitecture() { - return Build.HARDWARE; - } - - @Override - public String getOSFamily() { - return "Android"; - } - - @Override - public String getOSVersion() { - return Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")"; - } - - @Override - public String getManufacturer() { - return Build.MANUFACTURER; - } - - @Override - public String getModelName() { - return Build.MODEL; - } - - @Override - public String getPlayerVersion() { - return ExoPlayerLibraryInfo.VERSION; - } - - @Override - public String getDeviceId() { - return deviceId; - } - - @Override - public String getAppName() { - return appName; - } - - @Override - public String getAppVersion() { - return appVersion; - } - - @Override - public String getPluginName() { - return BuildConfig.MUX_PLUGIN_NAME; - } - - @Override - public String getPluginVersion() { - return BuildConfig.MUX_PLUGIN_VERSION; - } - - @Override - public String getPlayerSoftware() { - return EXO_SOFTWARE; - } - - @Override - public String getNetworkConnectionType() { - // Checking internet connectivity - Context context = contextRef.get(); - if (context == null) { - return null; - } - ConnectivityManager connectivityMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = null; - if (connectivityMgr != null) { - activeNetwork = connectivityMgr.getActiveNetworkInfo(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - NetworkCapabilities nc = connectivityMgr.getNetworkCapabilities(connectivityMgr.getActiveNetwork()); - if (nc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { - return CONNECTION_TYPE_WIRED; - } else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - return CONNECTION_TYPE_WIFI; - } else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - return CONNECTION_TYPE_CELLULAR; - } else { - return CONNECTION_TYPE_OTHER; - } - } else { - if (activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET) { - return CONNECTION_TYPE_WIRED; - } else if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { - return CONNECTION_TYPE_WIFI; - } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { - return CONNECTION_TYPE_CELLULAR; - } else { - return CONNECTION_TYPE_OTHER; - } - } - } - return null; - } - - @Override - public long getElapsedRealtime() { - return elapsedRealtime(); - } - - @Override - public void outputLog(String tag, String msg) { - Log.v(tag, msg); - } + @Override + public BandwidthMetricData onLoadCompleted(DataSpec dataSpec, int dataType, + Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, + long loadDurationMs, long bytesLoaded) { + BandwidthMetricData loadData = super.onLoadCompleted(dataSpec, dataType, trackFormat, + mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs, bytesLoaded); + if (loadData != null) { + switch (dataType) { + case C.DATA_TYPE_MANIFEST: + loadData.setRequestEventType("manifestLoaded"); + break; + case C.DATA_TYPE_MEDIA: + loadData.setRequestEventType("mediaFragmentLoaded"); + break; + default: + break; + } + } + return loadData; } - - class BandwidthMetric { - public BandwidthMetricData onLoadError(DataSpec dataSpec, int dataType, IOException e) { - BandwidthMetricData loadData = new BandwidthMetricData(); - loadData.setRequestError(e.toString()); - if (dataSpec != null && dataSpec.uri != null) { - loadData.setRequestUrl(dataSpec.uri.toString()); - loadData.setRequestHostName(dataSpec.uri.getHost()); - } - switch (dataType) { - case C.DATA_TYPE_MANIFEST: - loadData.setRequestType("manifest"); - break; - case C.DATA_TYPE_MEDIA: - loadData.setRequestType("media"); - break; - default: - return null; - } - loadData.setRequestErrorCode(null); - loadData.setRequestErrorText(e.getMessage()); - return loadData; - } - - public BandwidthMetricData onLoadCanceled(DataSpec dataSpec) { - BandwidthMetricData loadData = new BandwidthMetricData(); - loadData.setRequestCancel("genericLoadCanceled"); - if (dataSpec != null && dataSpec.uri != null) { - loadData.setRequestUrl(dataSpec.uri.toString()); - loadData.setRequestHostName(dataSpec.uri.getHost()); - } - loadData.setRequestType("media"); - return loadData; - } - - protected BandwidthMetricData onLoad(DataSpec dataSpec, int dataType, - Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, - long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) { - BandwidthMetricData loadData = new BandwidthMetricData(); - if (bytesLoaded > 0) { - loadData.setRequestBytesLoaded(bytesLoaded); - } - switch (dataType) { - case C.DATA_TYPE_MANIFEST: - loadData.setRequestType("manifest"); - break; - case C.DATA_TYPE_MEDIA: - loadData.setRequestType("media"); - break; - default: - return null; - } - loadData.setRequestResponseHeaders(null); - if (dataSpec != null && dataSpec.uri != null) { - loadData.setRequestHostName(dataSpec.uri.getHost()); - } - if (dataType == C.DATA_TYPE_MEDIA) { - loadData.setRequestMediaDuration(mediaEndTimeMs - mediaStartTimeMs); - } - if (trackFormat != null) { - loadData.setRequestCurrentLevel(null); - if (dataType == C.DATA_TYPE_MEDIA) { - loadData.setRequestMediaStartTime(mediaStartTimeMs); - } - loadData.setRequestVideoWidth(trackFormat.width); - loadData.setRequestVideoHeight(trackFormat.height); - } - loadData.setRequestRenditionLists(renditionList); - return loadData; - } - - public BandwidthMetricData onLoadStarted(DataSpec dataSpec, int dataType, - Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) { - BandwidthMetricData loadData = onLoad(dataSpec, dataType, trackFormat, - mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs, 0, 0); - if (loadData != null) { - loadData.setRequestResponseStart(elapsedRealtimeMs); - } - return loadData; - } - - public BandwidthMetricData onLoadCompleted(DataSpec dataSpec, int dataType, - Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, - long loadDurationMs, long bytesLoaded) { - BandwidthMetricData loadData = onLoad(dataSpec, dataType, trackFormat, - mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs, bytesLoaded); - if (loadData != null) { - loadData.setRequestResponseStart(elapsedRealtimeMs - loadDurationMs); - loadData.setRequestResponseEnd(elapsedRealtimeMs); - } - return loadData; - } + } + + class BandwidthMetricDispatcher { + + private final BandwidthMetric bandwidthMetricHls = new BandwidthMetricHls(); + private final BandwidthMetric bandwidthMetricDash = new BandwidthMetricDash(); + + public BandwidthMetric currentBandwidthMetric() { + switch (streamType) { + case C.TYPE_HLS: + return bandwidthMetricHls; + case C.TYPE_DASH: + return bandwidthMetricDash; + default: + break; + } + return null; } - class BandwidthMetricHls extends BandwidthMetric { - @Override - public BandwidthMetricData onLoadError(DataSpec dataSpec, int dataType, IOException e) { - BandwidthMetricData loadData = super.onLoadError(dataSpec, C.DATA_TYPE_MEDIA, e); - return loadData; - } - - @Override - public BandwidthMetricData onLoadCanceled(DataSpec dataSpec) { - BandwidthMetricData loadData = super.onLoadCanceled(dataSpec); - loadData.setRequestCancel("hlsFragLoadEmergencyAborted"); - return loadData; - } - - @Override - public BandwidthMetricData onLoadCompleted(DataSpec dataSpec, int dataType, - Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, - long loadDurationMs, long bytesLoaded) { - BandwidthMetricData loadData = super.onLoadCompleted(dataSpec, dataType, trackFormat, - mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs, bytesLoaded); - if (loadData != null) { - switch (dataType) { - case C.DATA_TYPE_MANIFEST: - loadData.setRequestEventType("hlsManifestLoaded"); - break; - case C.DATA_TYPE_MEDIA: - loadData.setRequestEventType("hlsFragBuffered"); - break; - default: - break; - } - if (trackFormat != null) - loadData.setRequestLabeledBitrate(trackFormat.bitrate); - } - return loadData; - } + public void onLoadError(DataSpec dataSpec, int dataType, IOException e) { + if (player == null || player.get() == null || muxStats == null + || currentBandwidthMetric() == null) { + return; + } + BandwidthMetricData loadData = currentBandwidthMetric().onLoadError(dataSpec, dataType, e); + dispatch(loadData); } - class BandwidthMetricDash extends BandwidthMetric { - @Override - public BandwidthMetricData onLoadStarted(DataSpec dataSpec, int dataType, - Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) { - BandwidthMetricData loadData = super.onLoadStarted(dataSpec, dataType, trackFormat, - mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs); - if (loadData != null) { - switch (dataType) { - case C.DATA_TYPE_MEDIA: - loadData.setRequestEventType("initFragmentLoaded"); - break; - default: - break; - } - } - return loadData; - } - - @Override - public BandwidthMetricData onLoadCompleted(DataSpec dataSpec, int dataType, - Format trackFormat, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, - long loadDurationMs, long bytesLoaded) { - BandwidthMetricData loadData = super.onLoadCompleted(dataSpec, dataType, trackFormat, - mediaStartTimeMs, mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs, bytesLoaded); - if (loadData != null) { - switch (dataType) { - case C.DATA_TYPE_MANIFEST: - loadData.setRequestEventType("manifestLoaded"); - break; - case C.DATA_TYPE_MEDIA: - loadData.setRequestEventType("mediaFragmentLoaded"); - break; - default: - break; - } - } - return loadData; - } + public void onLoadCanceled(DataSpec dataSpec) { + if (player == null || player.get() == null || muxStats == null + || currentBandwidthMetric() == null) { + return; + } + BandwidthMetricData loadData = currentBandwidthMetric().onLoadCanceled(dataSpec); + dispatch(loadData); } - class BandwidthMetricDispatcher { - private BandwidthMetric bandwidthMetricHls = new BandwidthMetricHls(); - private BandwidthMetric bandwidthMetricDash = new BandwidthMetricDash(); - - public BandwidthMetric currentBandwidthMetric() { - switch(streamType) { - case C.TYPE_HLS: - return bandwidthMetricHls; - case C.TYPE_DASH: - return bandwidthMetricDash; - default: - break; - } - return null; - } - - public void onLoadError(DataSpec dataSpec, int dataType, IOException e) { - if (player == null || player.get() == null || muxStats == null || currentBandwidthMetric() == null) { - return; - } - BandwidthMetricData loadData = currentBandwidthMetric().onLoadError(dataSpec, dataType, e); - dispatch(loadData); - } - - public void onLoadCanceled(DataSpec dataSpec) { - if (player == null || player.get() == null || muxStats == null || currentBandwidthMetric() == null) { - return; - } - BandwidthMetricData loadData = currentBandwidthMetric().onLoadCanceled(dataSpec); - dispatch(loadData); - } - - public void onLoadStarted(DataSpec dataSpec, int dataType, Format trackFormat, - long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) { - if (player == null || player.get() == null || muxStats == null || currentBandwidthMetric() == null) { - return; - } - currentBandwidthMetric().onLoadStarted(dataSpec, dataType, - trackFormat, mediaStartTimeMs, - mediaEndTimeMs, elapsedRealtimeMs); - } - - public void onLoadCompleted(DataSpec dataSpec, int dataType, Format trackFormat, - long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, - long bytesLoaded, Map> responseHeaders) { - if (player == null || player.get() == null || muxStats == null || currentBandwidthMetric() == null) { - return; - } - BandwidthMetricData loadData = currentBandwidthMetric().onLoadCompleted(dataSpec, - dataType, - trackFormat, mediaStartTimeMs, - mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs, bytesLoaded); + public void onLoadStarted(DataSpec dataSpec, int dataType, Format trackFormat, + long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) { + if (player == null || player.get() == null || muxStats == null + || currentBandwidthMetric() == null) { + return; + } + currentBandwidthMetric().onLoadStarted(dataSpec, dataType, + trackFormat, mediaStartTimeMs, + mediaEndTimeMs, elapsedRealtimeMs); + } - // Only append this data if we have some load data going on already - // TODO - this does not work correctly today, fix this and re-enable it. + public void onLoadCompleted(DataSpec dataSpec, int dataType, Format trackFormat, + long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, + long bytesLoaded, Map> responseHeaders) { + if (player == null || player.get() == null || muxStats == null + || currentBandwidthMetric() == null) { + return; + } + BandwidthMetricData loadData = currentBandwidthMetric().onLoadCompleted(dataSpec, + dataType, + trackFormat, mediaStartTimeMs, + mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs, bytesLoaded); + + // Only append this data if we have some load data going on already + // TODO - this does not work correctly today, fix this and re-enable it. // if (loadData != null && responseHeaders != null) { // Hashtable headers = parseHeaders(responseHeaders); // @@ -973,84 +991,86 @@ public void onLoadCompleted(DataSpec dataSpec, int dataType, Format trackFormat, // } // } - dispatch(loadData); - } - - public void onTracksChanged(TrackGroupArray trackGroups) { - if (player == null || player.get() == null || muxStats == null || currentBandwidthMetric() == null) { - return; - } - if (trackGroups.length > 0) { - for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { - TrackGroup trackGroup = trackGroups.get(groupIndex); - if (0 < trackGroup.length) { - Format trackFormat = trackGroup.getFormat(0); - if (trackFormat.containerMimeType != null && trackFormat.containerMimeType.contains("video")) { - List renditions = new ArrayList<>(); - for (int i = 0; i < trackGroup.length; i++) { - trackFormat = trackGroup.getFormat(i); - BandwidthMetricData.Rendition rendition = new BandwidthMetricData.Rendition(); - rendition.bitrate = trackFormat.bitrate; - rendition.width = trackFormat.width; - rendition.height = trackFormat.height; - renditions.add(rendition); - } - renditionList = renditions; - } - } - } - } - } + dispatch(loadData); + } - private void dispatch(BandwidthMetricData data) { - if (data != null) { - RequestBandwidthEvent playback = new RequestBandwidthEvent(null); - playback.setBandwidthMetricData(data); - MuxBaseExoPlayer.this.dispatch(playback); + public void onTracksChanged(TrackGroupArray trackGroups) { + if (player == null || player.get() == null || muxStats == null + || currentBandwidthMetric() == null) { + return; + } + if (trackGroups.length > 0) { + for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { + TrackGroup trackGroup = trackGroups.get(groupIndex); + if (0 < trackGroup.length) { + Format trackFormat = trackGroup.getFormat(0); + if (trackFormat.containerMimeType != null && trackFormat.containerMimeType + .contains("video")) { + List renditions = new ArrayList<>(); + for (int i = 0; i < trackGroup.length; i++) { + trackFormat = trackGroup.getFormat(i); + BandwidthMetricData.Rendition rendition = new BandwidthMetricData.Rendition(); + rendition.bitrate = trackFormat.bitrate; + rendition.width = trackFormat.width; + rendition.height = trackFormat.height; + renditions.add(rendition); + } + renditionList = renditions; } + } } + } + } - private Hashtable parseHeaders(Map> responseHeaders) { - if (responseHeaders == null || responseHeaders.size() == 0) { - return null; - } - - Hashtable headers = new Hashtable(); - for (String headerName : responseHeaders.keySet()) { - if (headerName == null) { - continue; - } - List headerValues = responseHeaders.get(headerName); - if (headerValues.size() == 1) { - headers.put(headerName, headerValues.get(0)); - } else if (headerValues.size() > 1) { - // In the case that there is more than one header, we squash - // it down to a single comma-separated value per RFC 2616 - // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - String headerValue = headerValues.get(0); - for (int i = 1; i < headerValues.size(); i++) { - headerValue = headerValue + ", " + headerValues.get(i); - } - headers.put(headerName, headerValue); - } - } - return headers; - } + private void dispatch(BandwidthMetricData data) { + if (data != null) { + RequestBandwidthEvent playback = new RequestBandwidthEvent(null); + playback.setBandwidthMetricData(data); + MuxBaseExoPlayer.this.dispatch(playback); + } } - private int pxToDp(int px) { - Context context = contextRef.get(); + private Hashtable parseHeaders(Map> responseHeaders) { + if (responseHeaders == null || responseHeaders.size() == 0) { + return null; + } + + Hashtable headers = new Hashtable(); + for (String headerName : responseHeaders.keySet()) { + if (headerName == null) { + continue; + } + List headerValues = responseHeaders.get(headerName); + if (headerValues.size() == 1) { + headers.put(headerName, headerValues.get(0)); + } else if (headerValues.size() > 1) { + // In the case that there is more than one header, we squash + // it down to a single comma-separated value per RFC 2616 + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + String headerValue = headerValues.get(0); + for (int i = 1; i < headerValues.size(); i++) { + headerValue = headerValue + ", " + headerValues.get(i); + } + headers.put(headerName, headerValue); + } + } + return headers; + } + } - // Bail out if we don't have the context - if (context == null) { - MuxLogger.d(TAG, "Error retrieving Context for logical resolution, using physical"); - return px; - } + private int pxToDp(int px) { + Context context = contextRef.get(); - DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - return (int)Math.ceil(px / displayMetrics.density); + // Bail out if we don't have the context + if (context == null) { + MuxLogger.d(TAG, "Error retrieving Context for logical resolution, using physical"); + return px; } - protected BandwidthMetricDispatcher bandwidthDispatcher = new BandwidthMetricDispatcher(); - protected List renditionList; + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + return (int) Math.ceil(px / displayMetrics.density); + } + + protected BandwidthMetricDispatcher bandwidthDispatcher = new BandwidthMetricDispatcher(); + protected List renditionList; } diff --git a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/MuxNetworkRequests.java b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/MuxNetworkRequests.java index 6bc4382b..3e2e0e7c 100644 --- a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/MuxNetworkRequests.java +++ b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/MuxNetworkRequests.java @@ -3,13 +3,8 @@ import android.net.Uri; import android.os.AsyncTask; import android.util.Log; - import com.mux.stats.sdk.core.util.MuxLogger; -import com.mux.stats.sdk.muxstats.INetworkRequest; import com.mux.stats.sdk.muxstats.compat.AsyncTaskCompat; - -import org.json.JSONObject; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -20,248 +15,260 @@ import java.util.Hashtable; import java.util.regex.Pattern; import java.util.zip.GZIPOutputStream; +import org.json.JSONObject; public class MuxNetworkRequests implements INetworkRequest { - private static final String TAG = "MuxNetworkRequests"; - private interface NetworkRequest { - URL getUrl(); - String getMethod(); - String getBody(); - Hashtable getHeaders(); - } + private static final String TAG = "MuxNetworkRequests"; - private static class GetRequest implements NetworkRequest { - private URL url; - private Hashtable headers; + private interface NetworkRequest { - GetRequest(URL url) { - this.url = url; - this.headers = new Hashtable(); - } + URL getUrl(); - GetRequest(URL url, Hashtable headers) { - this.url = url; - this.headers = headers == null ? new Hashtable() : headers; - } + String getMethod(); - @Override - public URL getUrl() { - return url; - } + String getBody(); - @Override - public String getMethod() { - return "GET"; - } + Hashtable getHeaders(); + } - @Override - public String getBody() { - return null; - } + private static class GetRequest implements NetworkRequest { - @Override - public Hashtable getHeaders() { - return headers; - } + private final URL url; + private final Hashtable headers; + + GetRequest(URL url) { + this.url = url; + this.headers = new Hashtable(); } - private static class PostRequest implements NetworkRequest { - private URL url; - private String body; - private Hashtable headers; + GetRequest(URL url, Hashtable headers) { + this.url = url; + this.headers = headers == null ? new Hashtable() : headers; + } - PostRequest(URL url, String body) { - this.url = url; - this.body = body == null ? "" : body; - this.headers = new Hashtable(); - } + @Override + public URL getUrl() { + return url; + } - PostRequest(URL url, String body, Hashtable headers) { - this.url = url; - this.body = body == null ? "" : body; - this.headers = headers == null ? new Hashtable() : headers; - } + @Override + public String getMethod() { + return "GET"; + } - @Override - public URL getUrl() { - return url; - } + @Override + public String getBody() { + return null; + } - @Override - public String getMethod() { - return "POST"; - } + @Override + public Hashtable getHeaders() { + return headers; + } + } - @Override - public String getBody() { - return body; - } + private static class PostRequest implements NetworkRequest { - @Override - public Hashtable getHeaders() { - return headers; - } - } + private final URL url; + private final String body; + private final Hashtable headers; - private static class NetworkTaskRunner extends AsyncTask { - private static final int READ_TIMEOUT_MS = 20 * 1000; - private static final int CONNECT_TIMEOUT_MS = 30 * 1000; - private static final int MAXIMUM_RETRY = 4; - private static final int BASE_TIME_BETWEEN_BEACONS = 5000; - private IMuxNetworkRequestsCompletion callback; - private int failureCount = 0; + PostRequest(URL url, String body) { + this.url = url; + this.body = body == null ? "" : body; + this.headers = new Hashtable(); + } - public NetworkTaskRunner(IMuxNetworkRequestsCompletion callback) { - this.callback = callback; - } + PostRequest(URL url, String body, Hashtable headers) { + this.url = url; + this.body = body == null ? "" : body; + this.headers = headers == null ? new Hashtable() : headers; + } - private long getNextBeaconTime() { - if (failureCount == 0) - return 0; - double factor = Math.pow(2, failureCount - 1); - factor = factor * Math.random(); - return (long)(1 + factor) * BASE_TIME_BETWEEN_BEACONS; - } + @Override + public URL getUrl() { + return url; + } - @Override - protected Void doInBackground(NetworkRequest... params) { - NetworkRequest request = params[0]; - URL url = request.getUrl(); - String method = request.getMethod(); - Hashtable headers = request.getHeaders(); - String body = request.getBody(); - - MuxLogger.d(TAG, "making " + method + " request to: " + url.toString()); - boolean successful = false; - while (!successful && failureCount < MAXIMUM_RETRY) { - try { - Thread.sleep(getNextBeaconTime()); - } catch (InterruptedException e) { - e.printStackTrace(); - } - successful = executeHttp(url, method, headers, body); - } - if (callback != null) { - callback.onComplete(successful); - } - return null; - } + @Override + public String getMethod() { + return "POST"; + } - private boolean executeHttp(URL url, String method, Hashtable headers, String body) { - HttpURLConnection conn = null; - InputStream stream = null; - boolean successful = true; - - try { - conn = (HttpURLConnection) url.openConnection(); - conn.setReadTimeout(READ_TIMEOUT_MS); - conn.setConnectTimeout(CONNECT_TIMEOUT_MS); - conn.setRequestMethod(method); - - // Load in the headers passed in the request - Enumeration headerKeys = headers.keys(); - boolean shouldGzip = false; - while (headerKeys.hasMoreElements()) { - String key = headerKeys.nextElement(); - String value = headers.get(key); - conn.setRequestProperty(key, value); - if (key.equalsIgnoreCase("Content-Encoding") && value.equalsIgnoreCase("gzip")) { - shouldGzip = true; - } - } - - // Handle the case where we have a POST and need to put the body in - if (method.equals("POST")) { - conn.setRequestProperty("Content-Type", "application/json"); - byte[] bytes = body.getBytes(); - if (shouldGzip) { - MuxLogger.d(TAG, "gzipping"); - bytes = gzip(bytes); - } - - OutputStream outputStream = conn.getOutputStream(); - outputStream.write(bytes); - outputStream.close(); - } - - conn.connect(); - stream = conn.getInputStream(); - MuxLogger.d(TAG, "got response: " + conn.getResponseCode()); - } catch (Exception e) { - Log.e(TAG, e.getMessage(), e); - successful = false; - failureCount++; - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException ioe) { - Log.e(TAG, ioe.getMessage(), ioe); - successful = false; - failureCount++; - } - } - if (conn != null) { - conn.disconnect(); - } - } - return successful; - } + @Override + public String getBody() { + return body; + } - private static byte[] gzip(byte[] input) throws Exception { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream); - gzipOutputStream.write(input); - gzipOutputStream.flush(); - gzipOutputStream.close(); - return byteArrayOutputStream.toByteArray(); - } + @Override + public Hashtable getHeaders() { + return headers; } + } - private String getAuthority(String propertykey) { - if (Pattern.matches("^[a-z0-9]+$", propertykey)) { - return propertykey + ".litix.io"; - } - return "img.litix.io"; + private static class NetworkTaskRunner extends AsyncTask { + + private static final int READ_TIMEOUT_MS = 20 * 1000; + private static final int CONNECT_TIMEOUT_MS = 30 * 1000; + private static final int MAXIMUM_RETRY = 4; + private static final int BASE_TIME_BETWEEN_BEACONS = 5000; + private final IMuxNetworkRequestsCompletion callback; + private int failureCount = 0; + + public NetworkTaskRunner(IMuxNetworkRequestsCompletion callback) { + this.callback = callback; } + private long getNextBeaconTime() { + if (failureCount == 0) { + return 0; + } + double factor = Math.pow(2, failureCount - 1); + factor = factor * Math.random(); + return (long) (1 + factor) * BASE_TIME_BETWEEN_BEACONS; + } @Override - public void get(URL url) { + protected Void doInBackground(NetworkRequest... params) { + NetworkRequest request = params[0]; + URL url = request.getUrl(); + String method = request.getMethod(); + Hashtable headers = request.getHeaders(); + String body = request.getBody(); + + MuxLogger.d(TAG, "making " + method + " request to: " + url.toString()); + boolean successful = false; + while (!successful && failureCount < MAXIMUM_RETRY) { try { - AsyncTaskCompat.executeParallel(new NetworkTaskRunner(null), new GetRequest(url)); - } catch (Exception e) { - Log.e(TAG, e.getMessage(), e); + Thread.sleep(getNextBeaconTime()); + } catch (InterruptedException e) { + e.printStackTrace(); } + successful = executeHttp(url, method, headers, body); + } + if (callback != null) { + callback.onComplete(successful); + } + return null; } - @Override - public void post(URL url, JSONObject body, Hashtable headers) { - try { - AsyncTaskCompat.executeParallel(new NetworkTaskRunner(null), new PostRequest(url, body.toString(), headers)); - } catch (Exception e) { - Log.e(TAG, e.getMessage(), e); + private boolean executeHttp(URL url, String method, Hashtable headers, + String body) { + HttpURLConnection conn = null; + InputStream stream = null; + boolean successful = true; + + try { + conn = (HttpURLConnection) url.openConnection(); + conn.setReadTimeout(READ_TIMEOUT_MS); + conn.setConnectTimeout(CONNECT_TIMEOUT_MS); + conn.setRequestMethod(method); + + // Load in the headers passed in the request + Enumeration headerKeys = headers.keys(); + boolean shouldGzip = false; + while (headerKeys.hasMoreElements()) { + String key = headerKeys.nextElement(); + String value = headers.get(key); + conn.setRequestProperty(key, value); + if (key.equalsIgnoreCase("Content-Encoding") && value.equalsIgnoreCase("gzip")) { + shouldGzip = true; + } } - } - @Override - public void postWithCompletion(String propertyKey, String body, - Hashtable headers, - INetworkRequest.IMuxNetworkRequestsCompletion callback) { - try { - if (propertyKey != null) { - Uri.Builder uriBuilder = new Uri.Builder(); - uriBuilder.scheme("https").authority(this.getAuthority(propertyKey)).path( - "android"); - AsyncTaskCompat.executeParallel(new NetworkTaskRunner(callback), - new PostRequest(new URL(uriBuilder.build().toString()), body, headers)); - } else { - throw new Exception("propertyKey is null"); - } - } catch (Exception e) { - Log.e(TAG, e.getMessage(), e); - callback.onComplete(true); + // Handle the case where we have a POST and need to put the body in + if (method.equals("POST")) { + conn.setRequestProperty("Content-Type", "application/json"); + byte[] bytes = body.getBytes(); + if (shouldGzip) { + MuxLogger.d(TAG, "gzipping"); + bytes = gzip(bytes); + } + + OutputStream outputStream = conn.getOutputStream(); + outputStream.write(bytes); + outputStream.close(); } + + conn.connect(); + stream = conn.getInputStream(); + MuxLogger.d(TAG, "got response: " + conn.getResponseCode()); + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + successful = false; + failureCount++; + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException ioe) { + Log.e(TAG, ioe.getMessage(), ioe); + successful = false; + failureCount++; + } + } + if (conn != null) { + conn.disconnect(); + } + } + return successful; + } + + private static byte[] gzip(byte[] input) throws Exception { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream); + gzipOutputStream.write(input); + gzipOutputStream.flush(); + gzipOutputStream.close(); + return byteArrayOutputStream.toByteArray(); + } + } + + private String getAuthority(String propertykey) { + if (Pattern.matches("^[a-z0-9]+$", propertykey)) { + return propertykey + ".litix.io"; + } + return "img.litix.io"; + } + + + @Override + public void get(URL url) { + try { + AsyncTaskCompat.executeParallel(new NetworkTaskRunner(null), new GetRequest(url)); + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + } + } + + @Override + public void post(URL url, JSONObject body, Hashtable headers) { + try { + AsyncTaskCompat.executeParallel(new NetworkTaskRunner(null), + new PostRequest(url, body.toString(), headers)); + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + } + } + + @Override + public void postWithCompletion(String propertyKey, String body, + Hashtable headers, + INetworkRequest.IMuxNetworkRequestsCompletion callback) { + try { + if (propertyKey != null) { + Uri.Builder uriBuilder = new Uri.Builder(); + uriBuilder.scheme("https").authority(this.getAuthority(propertyKey)).path( + "android"); + AsyncTaskCompat.executeParallel(new NetworkTaskRunner(callback), + new PostRequest(new URL(uriBuilder.build().toString()), body, headers)); + } else { + throw new Exception("propertyKey is null"); + } + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + callback.onComplete(true); } + } } diff --git a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/compat/AsyncTaskCompat.java b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/compat/AsyncTaskCompat.java index 483aa32f..b5be2dbd 100644 --- a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/compat/AsyncTaskCompat.java +++ b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/compat/AsyncTaskCompat.java @@ -4,31 +4,32 @@ import android.os.Build; /** - * Helper for accessing features in {@link android.os.AsyncTask} - * introduced after API level 4 in a backwards compatible fashion. + * Helper for accessing features in {@link android.os.AsyncTask} introduced after API level 4 in a + * backwards compatible fashion. */ public class AsyncTaskCompat { - /** - * Executes the task with the specified parameters, allowing multiple tasks to run in parallel - * on a pool of threads managed by {@link android.os.AsyncTask}. - * - * @param task The {@link android.os.AsyncTask} to execute. - * @param params The parameters of the task. - * @return the instance of AsyncTask. - */ - public static AsyncTask executeParallel( - AsyncTask task, - Params... params) { - if (task == null) { - throw new IllegalArgumentException("task can not be null"); - } - if (Build.VERSION.SDK_INT >= 11) { - // From API 11 onwards, we need to manually select the THREAD_POOL_EXECUTOR - AsyncTaskCompatHoneycomb.executeParallel(task, params); - } else { - // Before API 11, all tasks were run in parallel - task.execute(params); - } - return task; + + /** + * Executes the task with the specified parameters, allowing multiple tasks to run in parallel on + * a pool of threads managed by {@link android.os.AsyncTask}. + * + * @param task The {@link android.os.AsyncTask} to execute. + * @param params The parameters of the task. + * @return the instance of AsyncTask. + */ + public static AsyncTask executeParallel( + AsyncTask task, + Params... params) { + if (task == null) { + throw new IllegalArgumentException("task can not be null"); + } + if (Build.VERSION.SDK_INT >= 11) { + // From API 11 onwards, we need to manually select the THREAD_POOL_EXECUTOR + AsyncTaskCompatHoneycomb.executeParallel(task, params); + } else { + // Before API 11, all tasks were run in parallel + task.execute(params); } + return task; + } } diff --git a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/compat/AsyncTaskCompatHoneycomb.java b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/compat/AsyncTaskCompatHoneycomb.java index 500dd138..7f87dd56 100644 --- a/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/compat/AsyncTaskCompatHoneycomb.java +++ b/MuxExoPlayer/src/main/java/com/mux/stats/sdk/muxstats/compat/AsyncTaskCompatHoneycomb.java @@ -6,9 +6,10 @@ * Implementation of AsyncTask compatibility that can call Honeycomb APIs. */ public class AsyncTaskCompatHoneycomb { - public static void executeParallel( - AsyncTask task, - Params... params) { - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); - } + + public static void executeParallel( + AsyncTask task, + Params... params) { + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); + } } \ No newline at end of file diff --git a/MuxExoPlayer/src/main/res/drawable/ic_launcher_background.xml b/MuxExoPlayer/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 0d025f9b..00000000 --- a/MuxExoPlayer/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MuxExoPlayer/src/main/res/layout/activity_main.xml b/MuxExoPlayer/src/main/res/layout/activity_main.xml deleted file mode 100644 index 9020b676..00000000 --- a/MuxExoPlayer/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/MuxExoPlayer/src/main/res/values/colors.xml b/MuxExoPlayer/src/main/res/values/colors.xml deleted file mode 100644 index 69b22338..00000000 --- a/MuxExoPlayer/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - #008577 - #00574B - #D81B60 - diff --git a/MuxExoPlayer/src/main/res/values/strings.xml b/MuxExoPlayer/src/main/res/values/strings.xml deleted file mode 100644 index 741489c8..00000000 --- a/MuxExoPlayer/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - MuxStats - diff --git a/MuxExoPlayer/src/main/res/values/styles.xml b/MuxExoPlayer/src/main/res/values/styles.xml deleted file mode 100644 index f26eddfd..00000000 --- a/MuxExoPlayer/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/MuxExoPlayer/src/r2_10_6/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java b/MuxExoPlayer/src/r2_10_6/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java index 08d7211d..d29c30fa 100644 --- a/MuxExoPlayer/src/r2_10_6/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java +++ b/MuxExoPlayer/src/r2_10_6/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java @@ -2,7 +2,6 @@ import android.content.Context; import android.view.Surface; - import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; @@ -23,424 +22,429 @@ import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; import com.mux.stats.sdk.core.model.CustomerViewData; - import java.io.IOException; -public class MuxStatsExoPlayer extends MuxBaseExoPlayer implements AnalyticsListener, Player.EventListener{ - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, null, true); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, true); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - boolean sentryEnabled) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, null, sentryEnabled); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData, boolean sentryEnabled) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, - sentryEnabled, new MuxNetworkRequests()); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData, boolean sentryEnabled, - INetworkRequest networkRequest) { - super(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, - sentryEnabled, networkRequest); +public class MuxStatsExoPlayer extends MuxBaseExoPlayer implements AnalyticsListener, + Player.EventListener { + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, null, true); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, true); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + boolean sentryEnabled) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, null, sentryEnabled); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData, boolean sentryEnabled) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, + sentryEnabled, new MuxNetworkRequests()); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData, boolean sentryEnabled, + INetworkRequest networkRequest) { + super(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, + sentryEnabled, networkRequest); + + if (player instanceof SimpleExoPlayer) { + ((SimpleExoPlayer) player).addAnalyticsListener(this); + } else { + player.addListener(this); + } + if (player.getPlaybackState() == Player.STATE_BUFFERING) { + // playback started before muxStats was initialized + play(); + buffering(); + } else if (player.getPlaybackState() == Player.STATE_READY) { + // We have to simulate all the events we expect to see here, even though not ideal + play(); + buffering(); + playing(); + } + } + + @Override + public void release() { + if (player != null && this.player.get() != null) { + ExoPlayer player = this.player.get(); + if (player instanceof SimpleExoPlayer) { + ((SimpleExoPlayer) player).removeAnalyticsListener(this); + } else { + player.removeListener(this); + } + } + super.release(); + } + + @Override + public void onPlayerStateChanged(EventTime eventTime, boolean playWhenReady, + int playbackState) { + onPlayerStateChanged(playWhenReady, playbackState); + } + + @Override + public void onTimelineChanged(EventTime eventTime, int reason) { + onTimelineChanged(eventTime.timeline, null, reason); + } + + @Override + public void onPositionDiscontinuity(EventTime eventTime, int reason) { + onPositionDiscontinuity(reason); + } + + @Override + public void onSeekStarted(EventTime eventTime) { + seeking(); + } + + @Override + public void onSeekProcessed(EventTime eventTime) { + onSeekProcessed(); + } + + @Override + public void onPlaybackParametersChanged(EventTime eventTime, + PlaybackParameters playbackParameters) { + onPlaybackParametersChanged(playbackParameters); + } + + @Override + public void onRepeatModeChanged(EventTime eventTime, int repeatMode) { + onRepeatModeChanged(repeatMode); + } + + @Override + public void onShuffleModeChanged(EventTime eventTime, + boolean shuffleModeEnabled) { + onShuffleModeEnabledChanged(shuffleModeEnabled); + } + + @Override + public void onLoadingChanged(EventTime eventTime, boolean isLoading) { + onLoadingChanged(isLoading); + } + + @Override + public void onPlayerError(EventTime eventTime, ExoPlaybackException error) { + onPlayerError(error); + } + + @Override + public void onTracksChanged(EventTime eventTime, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections) { + onTracksChanged(trackGroups, trackSelections); + } + + @Override + public void onLoadStarted(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadStarted(loadEventInfo.dataSpec, mediaLoadData.dataType, + mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, + mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs); + } + + @Override + public void onLoadCompleted(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadCompleted(loadEventInfo.dataSpec, mediaLoadData.dataType, + mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, + mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs, + loadEventInfo.loadDurationMs, loadEventInfo.bytesLoaded, + loadEventInfo.responseHeaders); + } + + @Override + public void onLoadCanceled(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadCanceled(loadEventInfo.dataSpec); + } + + @Override + public void onLoadError(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData, IOException e, + boolean wasCanceled) { + bandwidthDispatcher.onLoadError(loadEventInfo.dataSpec, mediaLoadData.dataType, e); + } - if (player instanceof SimpleExoPlayer) { - ((SimpleExoPlayer) player).addAnalyticsListener(this); - } else { - player.addListener(this); - } - if (player.getPlaybackState() == Player.STATE_BUFFERING) { - // playback started before muxStats was initialized - play(); - buffering(); - } else if (player.getPlaybackState() == Player.STATE_READY) { - // We have to simulate all the events we expect to see here, even though not ideal - play(); - buffering(); - playing(); - } + @Override + public void onDownstreamFormatChanged(EventTime eventTime, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + if (mediaLoadData.trackFormat != null && mediaLoadData.trackFormat.containerMimeType != null) { + mimeType = mediaLoadData.trackFormat.containerMimeType; } + } - @Override - public void release() { - if (player != null && this.player.get() != null) { - ExoPlayer player = this.player.get(); - if (player instanceof SimpleExoPlayer) { - ((SimpleExoPlayer) player).removeAnalyticsListener(this); - } else { - player.removeListener(this); - } - } - super.release(); - } + @Override + public void onUpstreamDiscarded(EventTime eventTime, + MediaSourceEventListener.MediaLoadData mediaLoadData) { - @Override - public void onPlayerStateChanged(EventTime eventTime, boolean playWhenReady, - int playbackState) { - onPlayerStateChanged(playWhenReady, playbackState); - } + } - @Override - public void onTimelineChanged(EventTime eventTime, int reason) { - onTimelineChanged(eventTime.timeline, null, reason); - } + @Override + public void onMediaPeriodCreated(EventTime eventTime) { - @Override - public void onPositionDiscontinuity(EventTime eventTime, int reason) { - onPositionDiscontinuity(reason); - } + } - @Override - public void onSeekStarted(EventTime eventTime) { - seeking(); - } + @Override + public void onMediaPeriodReleased(EventTime eventTime) { - @Override - public void onSeekProcessed(EventTime eventTime) { - onSeekProcessed(); - } + } - @Override - public void onPlaybackParametersChanged(EventTime eventTime, - PlaybackParameters playbackParameters) { - onPlaybackParametersChanged(playbackParameters); - } + @Override + public void onReadingStarted(EventTime eventTime) { - @Override - public void onRepeatModeChanged(EventTime eventTime, int repeatMode) { - onRepeatModeChanged(repeatMode); - } + } - @Override - public void onShuffleModeChanged(EventTime eventTime, - boolean shuffleModeEnabled) { - onShuffleModeEnabledChanged(shuffleModeEnabled); - } + @Override + public void onBandwidthEstimate(EventTime eventTime, int totalLoadTimeMs, + long totalBytesLoaded, long bitrateEstimate) { - @Override - public void onLoadingChanged(EventTime eventTime, boolean isLoading) { - onLoadingChanged(isLoading); - } + } - @Override - public void onPlayerError(EventTime eventTime, ExoPlaybackException error) { - onPlayerError(error); - } + @Override + public void onMetadata(EventTime eventTime, Metadata metadata) { - @Override - public void onTracksChanged(EventTime eventTime, TrackGroupArray trackGroups, - TrackSelectionArray trackSelections) { - onTracksChanged(trackGroups, trackSelections); - } + } - @Override - public void onLoadStarted(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadStarted(loadEventInfo.dataSpec, mediaLoadData.dataType, - mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, - mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs); - } + @Override + public void onDecoderEnabled(EventTime eventTime, int trackType, + DecoderCounters decoderCounters) { - @Override - public void onLoadCompleted(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadCompleted(loadEventInfo.dataSpec, mediaLoadData.dataType, - mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, - mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs, - loadEventInfo.loadDurationMs, loadEventInfo.bytesLoaded, - loadEventInfo.responseHeaders); - } + } - @Override - public void onLoadCanceled(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadCanceled(loadEventInfo.dataSpec); - } + @Override + public void onDecoderInitialized(EventTime eventTime, int trackType, + String decoderName, long initializationDurationMs) { - @Override - public void onLoadError(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData, IOException e, - boolean wasCanceled) { - bandwidthDispatcher.onLoadError(loadEventInfo.dataSpec, mediaLoadData.dataType, e); - } + } - @Override - public void onDownstreamFormatChanged(EventTime eventTime, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - if (mediaLoadData.trackFormat != null && mediaLoadData.trackFormat.containerMimeType != null) { - mimeType = mediaLoadData.trackFormat.containerMimeType; - } + @Override + public void onDecoderInputFormatChanged(EventTime eventTime, int trackType, + Format format) { + if (trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_DEFAULT) { + handleRenditionChange(format); } + } - @Override - public void onUpstreamDiscarded(EventTime eventTime, - MediaSourceEventListener.MediaLoadData mediaLoadData) { + @Override + public void onDecoderDisabled(EventTime eventTime, int trackType, + DecoderCounters decoderCounters) { - } + } - @Override - public void onMediaPeriodCreated(EventTime eventTime) { + @Override + public void onAudioSessionId(EventTime eventTime, int audioSessionId) { - } + } - @Override - public void onMediaPeriodReleased(EventTime eventTime) { + @Override + public void onAudioUnderrun(EventTime eventTime, int bufferSize, + long bufferSizeMs, long elapsedSinceLastFeedMs) { - } + } - @Override - public void onReadingStarted(EventTime eventTime) { + @Override + public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, + long elapsedMs) { - } + } - @Override - public void onBandwidthEstimate(EventTime eventTime, int totalLoadTimeMs, - long totalBytesLoaded, long bitrateEstimate) { + @Override + public void onVideoSizeChanged(EventTime eventTime, int width, int height, + int unappliedRotationDegrees, float pixelWidthHeightRatio) { + sourceWidth = width; + sourceHeight = height; + } - } + @Override + public void onRenderedFirstFrame(EventTime eventTime, Surface surface) { - @Override - public void onMetadata(EventTime eventTime, Metadata metadata) { + } - } + @Override + public void onDrmKeysLoaded(EventTime eventTime) { - @Override - public void onDecoderEnabled(EventTime eventTime, int trackType, - DecoderCounters decoderCounters) { + } - } + @Override + public void onDrmSessionManagerError(EventTime eventTime, Exception e) { + internalError(new MuxErrorException(ERROR_DRM, "DrmSessionManagerError - " + e.getMessage())); + } - @Override - public void onDecoderInitialized(EventTime eventTime, int trackType, - String decoderName, long initializationDurationMs) { + @Override + public void onDrmKeysRestored(EventTime eventTime) { - } + } - @Override - public void onDecoderInputFormatChanged(EventTime eventTime, int trackType, - Format format) { - if (trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_DEFAULT) { - handleRenditionChange(format); - } - } + @Override + public void onDrmKeysRemoved(EventTime eventTime) { - @Override - public void onDecoderDisabled(EventTime eventTime, int trackType, - DecoderCounters decoderCounters) { + } + @Override + public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { + if (timeline != null && timeline.getWindowCount() > 0) { + Timeline.Window window = new Timeline.Window(); + timeline.getWindow(0, window); + sourceDuration = window.getDurationMs(); } + } - @Override - public void onAudioSessionId(EventTime eventTime, int audioSessionId) { + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + bandwidthDispatcher.onTracksChanged(trackGroups); + configurePlaybackHeadUpdateInterval(); + } - } + @Override + public void onLoadingChanged(boolean isLoading) { - @Override - public void onAudioUnderrun(EventTime eventTime, int bufferSize, - long bufferSizeMs, long elapsedSinceLastFeedMs) { + } + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + PlayerState state = this.getState(); + if (state == PlayerState.PLAYING_ADS) { + // Ignore all normal events while playing ads + return; } - - @Override - public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, - long elapsedMs) { - - } - - @Override - public void onVideoSizeChanged(EventTime eventTime, int width, int height, - int unappliedRotationDegrees, float pixelWidthHeightRatio) { - sourceWidth = width; - sourceHeight = height; - } - - @Override - public void onRenderedFirstFrame(EventTime eventTime, Surface surface) { - - } - - @Override - public void onDrmKeysLoaded(EventTime eventTime) { - - } - - @Override - public void onDrmSessionManagerError(EventTime eventTime, Exception e) { - internalError(new MuxErrorException(ERROR_DRM, "DrmSessionManagerError - " + e.getMessage())); - } - - @Override - public void onDrmKeysRestored(EventTime eventTime) { - - } - - @Override - public void onDrmKeysRemoved(EventTime eventTime) { - - } - - @Override - public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { - if (timeline != null && timeline.getWindowCount() > 0) { - Timeline.Window window = new Timeline.Window(); - timeline.getWindow(0, window); - sourceDuration = window.getDurationMs(); + switch (playbackState) { + case Player.STATE_BUFFERING: + // We have entered buffering + buffering(); + // If we are expected to playWhenReady, signal the play event + if (playWhenReady) { + play(); + } else if (state != PlayerState.PAUSED) { + pause(); } - } - - @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - bandwidthDispatcher.onTracksChanged(trackGroups); - configurePlaybackHeadUpdateInterval(); - } - - @Override - public void onLoadingChanged(boolean isLoading) { - - } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - PlayerState state = this.getState(); - if (state == PlayerState.PLAYING_ADS) { - // Ignore all normal events while playing ads - return; - } - switch (playbackState) { - case Player.STATE_BUFFERING: - // We have entered buffering - buffering(); - // If we are expected to playWhenReady, signal the play event - if (playWhenReady) { - play(); - } else if (state != PlayerState.PAUSED) { - pause(); - } - break; - case Player.STATE_ENDED: - ended(); - break; - case Player.STATE_READY: - // By the time we get here, it depends on playWhenReady to know if we're playing - if (playWhenReady) { - playing(); - } else if (state != PlayerState.PAUSED) { - pause(); - } - break; - case Player.STATE_IDLE: - default: - // don't care - break; + break; + case Player.STATE_ENDED: + ended(); + break; + case Player.STATE_READY: + // By the time we get here, it depends on playWhenReady to know if we're playing + if (playWhenReady) { + playing(); + } else if (state != PlayerState.PAUSED) { + pause(); } - } - - @Override - public void onRepeatModeChanged(int repeatMode) { - - } - - @Override - public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { - - } - - @Override - public void onPlayerError(ExoPlaybackException e) { - if (e.type == ExoPlaybackException.TYPE_RENDERER) { - Exception cause = e.getRendererException(); - if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { - MediaCodecRenderer.DecoderInitializationException die = (MediaCodecRenderer.DecoderInitializationException) cause; - if (die.decoderName == null) { - if (die.getCause() instanceof MediaCodecUtil.DecoderQueryException) { - internalError(new MuxErrorException(e.type, "Unable to query device decoders")); - } else if (die.secureDecoderRequired) { - internalError(new MuxErrorException(e.type, "No secure decoder for " + die.mimeType)); - } else { - internalError(new MuxErrorException(e.type, "No decoder for " + die.mimeType)); - } - } else { - internalError(new MuxErrorException(e.type, "Unable to instantiate decoder for " + die.mimeType)); - } - } - else { - internalError(new MuxErrorException(e.type, cause.getClass().getCanonicalName() + " - " + cause.getMessage())); - } - } else if (e.type == ExoPlaybackException.TYPE_SOURCE) { - Exception error = e.getSourceException(); - internalError(new MuxErrorException(e.type, error.getClass().getCanonicalName() + " - " + error.getMessage())); - } else if (e.type == ExoPlaybackException.TYPE_UNEXPECTED) { - Exception error = e.getUnexpectedException(); - internalError(new MuxErrorException(e.type, error.getClass().getCanonicalName() + " - " + error.getMessage())); + break; + case Player.STATE_IDLE: + default: + // don't care + break; + } + } + + @Override + public void onRepeatModeChanged(int repeatMode) { + + } + + @Override + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { + + } + + @Override + public void onPlayerError(ExoPlaybackException e) { + if (e.type == ExoPlaybackException.TYPE_RENDERER) { + Exception cause = e.getRendererException(); + if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { + MediaCodecRenderer.DecoderInitializationException die = (MediaCodecRenderer.DecoderInitializationException) cause; + if (die.decoderName == null) { + if (die.getCause() instanceof MediaCodecUtil.DecoderQueryException) { + internalError(new MuxErrorException(e.type, "Unable to query device decoders")); + } else if (die.secureDecoderRequired) { + internalError(new MuxErrorException(e.type, "No secure decoder for " + die.mimeType)); + } else { + internalError(new MuxErrorException(e.type, "No decoder for " + die.mimeType)); + } } else { - internalError(e); + internalError( + new MuxErrorException(e.type, "Unable to instantiate decoder for " + die.mimeType)); } + } else { + internalError(new MuxErrorException(e.type, + cause.getClass().getCanonicalName() + " - " + cause.getMessage())); + } + } else if (e.type == ExoPlaybackException.TYPE_SOURCE) { + Exception error = e.getSourceException(); + internalError(new MuxErrorException(e.type, + error.getClass().getCanonicalName() + " - " + error.getMessage())); + } else if (e.type == ExoPlaybackException.TYPE_UNEXPECTED) { + Exception error = e.getUnexpectedException(); + internalError(new MuxErrorException(e.type, + error.getClass().getCanonicalName() + " - " + error.getMessage())); + } else { + internalError(e); } + } - @Override - public void onPositionDiscontinuity(int reason) { - if (reason == Player.DISCONTINUITY_REASON_SEEK) { - if ( state == PlayerState.PAUSED || !playItemHaveVideoTrack ) { - seeked(false); - } - } + @Override + public void onPositionDiscontinuity(int reason) { + if (reason == Player.DISCONTINUITY_REASON_SEEK) { + if (state == PlayerState.PAUSED || !playItemHaveVideoTrack) { + seeked(false); + } } + } - @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - } + } - @Override - public void onSeekProcessed() { - } + @Override + public void onSeekProcessed() { + } - @Override - public void onSurfaceSizeChanged(AnalyticsListener.EventTime eventTime, int width, int height) { + @Override + public void onSurfaceSizeChanged(AnalyticsListener.EventTime eventTime, int width, int height) { - } + } - @Override - public void onIsPlayingChanged(AnalyticsListener.EventTime eventTime, boolean isPlaying) { + @Override + public void onIsPlayingChanged(AnalyticsListener.EventTime eventTime, boolean isPlaying) { - } + } - @Override - public void onAudioAttributesChanged(AnalyticsListener.EventTime eventTime, AudioAttributes audioAttributes) { + @Override + public void onAudioAttributesChanged(AnalyticsListener.EventTime eventTime, + AudioAttributes audioAttributes) { - } + } - @Override - public void onPlaybackSuppressionReasonChanged(AnalyticsListener.EventTime eventTime, int playbackSuppressionReason) { + @Override + public void onPlaybackSuppressionReasonChanged(AnalyticsListener.EventTime eventTime, + int playbackSuppressionReason) { - } + } - @Override - public void onVolumeChanged(AnalyticsListener.EventTime eventTime, float volume) { + @Override + public void onVolumeChanged(AnalyticsListener.EventTime eventTime, float volume) { - } + } } diff --git a/MuxExoPlayer/src/r2_11_1/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java b/MuxExoPlayer/src/r2_11_1/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java index 7e203ec3..eeebdb0d 100644 --- a/MuxExoPlayer/src/r2_11_1/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java +++ b/MuxExoPlayer/src/r2_11_1/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java @@ -2,7 +2,6 @@ import android.content.Context; import android.view.Surface; - import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; @@ -23,423 +22,428 @@ import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; import com.mux.stats.sdk.core.model.CustomerViewData; - import java.io.IOException; -public class MuxStatsExoPlayer extends MuxBaseExoPlayer implements AnalyticsListener, Player.EventListener{ - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, null, true); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, true); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - boolean sentryEnabled) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, null, sentryEnabled); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData, boolean sentryEnabled) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, - sentryEnabled, new MuxNetworkRequests()); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData, boolean sentryEnabled, - INetworkRequest networkRequest) { - super(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, - sentryEnabled, networkRequest); - if (player instanceof SimpleExoPlayer) { - ((SimpleExoPlayer) player).addAnalyticsListener(this); - } else { - player.addListener(this); - } - if (player.getPlaybackState() == Player.STATE_BUFFERING) { - // playback started before muxStats was initialized - play(); - buffering(); - } else if (player.getPlaybackState() == Player.STATE_READY) { - // We have to simulate all the events we expect to see here, even though not ideal - play(); - buffering(); - playing(); - } - } - - @Override - public void release() { - if (player != null && this.player.get() != null) { - ExoPlayer player = this.player.get(); - if (player instanceof SimpleExoPlayer) { - ((SimpleExoPlayer) player).removeAnalyticsListener(this); - } else { - player.removeListener(this); - } - } - super.release(); - } - - @Override - public void onPlayerStateChanged(EventTime eventTime, boolean playWhenReady, - int playbackState) { - onPlayerStateChanged(playWhenReady, playbackState); - } - - @Override - public void onTimelineChanged(EventTime eventTime, int reason) { - onTimelineChanged(eventTime.timeline, null, reason); - } - - @Override - public void onPositionDiscontinuity(EventTime eventTime, int reason) { - onPositionDiscontinuity(reason); - } - - @Override - public void onSeekStarted(EventTime eventTime) { - seeking(); - } - - @Override - public void onSeekProcessed(EventTime eventTime) { - onSeekProcessed(); - } - - @Override - public void onPlaybackParametersChanged(EventTime eventTime, - PlaybackParameters playbackParameters) { - onPlaybackParametersChanged(playbackParameters); - } - - @Override - public void onRepeatModeChanged(EventTime eventTime, int repeatMode) { - onRepeatModeChanged(repeatMode); - } - - @Override - public void onShuffleModeChanged(EventTime eventTime, - boolean shuffleModeEnabled) { - onShuffleModeEnabledChanged(shuffleModeEnabled); - } - - @Override - public void onLoadingChanged(EventTime eventTime, boolean isLoading) { - onLoadingChanged(isLoading); - } - - @Override - public void onPlayerError(EventTime eventTime, ExoPlaybackException error) { - onPlayerError(error); - } - - @Override - public void onTracksChanged(EventTime eventTime, TrackGroupArray trackGroups, - TrackSelectionArray trackSelections) { - onTracksChanged(trackGroups, trackSelections); - } - - @Override - public void onLoadStarted(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadStarted(loadEventInfo.dataSpec, mediaLoadData.dataType, - mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, - mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs); - } - - @Override - public void onLoadCompleted(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadCompleted(loadEventInfo.dataSpec, mediaLoadData.dataType, - mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, - mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs, - loadEventInfo.loadDurationMs, loadEventInfo.bytesLoaded, - loadEventInfo.responseHeaders); - } +public class MuxStatsExoPlayer extends MuxBaseExoPlayer implements AnalyticsListener, + Player.EventListener { + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, null, true); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, true); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + boolean sentryEnabled) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, null, sentryEnabled); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData, boolean sentryEnabled) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, + sentryEnabled, new MuxNetworkRequests()); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData, boolean sentryEnabled, + INetworkRequest networkRequest) { + super(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, + sentryEnabled, networkRequest); + if (player instanceof SimpleExoPlayer) { + ((SimpleExoPlayer) player).addAnalyticsListener(this); + } else { + player.addListener(this); + } + if (player.getPlaybackState() == Player.STATE_BUFFERING) { + // playback started before muxStats was initialized + play(); + buffering(); + } else if (player.getPlaybackState() == Player.STATE_READY) { + // We have to simulate all the events we expect to see here, even though not ideal + play(); + buffering(); + playing(); + } + } + + @Override + public void release() { + if (player != null && this.player.get() != null) { + ExoPlayer player = this.player.get(); + if (player instanceof SimpleExoPlayer) { + ((SimpleExoPlayer) player).removeAnalyticsListener(this); + } else { + player.removeListener(this); + } + } + super.release(); + } + + @Override + public void onPlayerStateChanged(EventTime eventTime, boolean playWhenReady, + int playbackState) { + onPlayerStateChanged(playWhenReady, playbackState); + } + + @Override + public void onTimelineChanged(EventTime eventTime, int reason) { + onTimelineChanged(eventTime.timeline, null, reason); + } + + @Override + public void onPositionDiscontinuity(EventTime eventTime, int reason) { + onPositionDiscontinuity(reason); + } + + @Override + public void onSeekStarted(EventTime eventTime) { + seeking(); + } + + @Override + public void onSeekProcessed(EventTime eventTime) { + onSeekProcessed(); + } + + @Override + public void onPlaybackParametersChanged(EventTime eventTime, + PlaybackParameters playbackParameters) { + onPlaybackParametersChanged(playbackParameters); + } + + @Override + public void onRepeatModeChanged(EventTime eventTime, int repeatMode) { + onRepeatModeChanged(repeatMode); + } + + @Override + public void onShuffleModeChanged(EventTime eventTime, + boolean shuffleModeEnabled) { + onShuffleModeEnabledChanged(shuffleModeEnabled); + } + + @Override + public void onLoadingChanged(EventTime eventTime, boolean isLoading) { + onLoadingChanged(isLoading); + } + + @Override + public void onPlayerError(EventTime eventTime, ExoPlaybackException error) { + onPlayerError(error); + } + + @Override + public void onTracksChanged(EventTime eventTime, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections) { + onTracksChanged(trackGroups, trackSelections); + } + + @Override + public void onLoadStarted(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadStarted(loadEventInfo.dataSpec, mediaLoadData.dataType, + mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, + mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs); + } + + @Override + public void onLoadCompleted(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadCompleted(loadEventInfo.dataSpec, mediaLoadData.dataType, + mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, + mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs, + loadEventInfo.loadDurationMs, loadEventInfo.bytesLoaded, + loadEventInfo.responseHeaders); + } + + @Override + public void onLoadCanceled(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadCanceled(loadEventInfo.dataSpec); + } + + @Override + public void onLoadError(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData, IOException e, + boolean wasCanceled) { + bandwidthDispatcher.onLoadError(loadEventInfo.dataSpec, mediaLoadData.dataType, e); + } - @Override - public void onLoadCanceled(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadCanceled(loadEventInfo.dataSpec); + @Override + public void onDownstreamFormatChanged(EventTime eventTime, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + if (mediaLoadData.trackFormat != null && mediaLoadData.trackFormat.containerMimeType != null) { + mimeType = mediaLoadData.trackFormat.containerMimeType; } + } - @Override - public void onLoadError(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData, IOException e, - boolean wasCanceled) { - bandwidthDispatcher.onLoadError(loadEventInfo.dataSpec, mediaLoadData.dataType, e); - } + @Override + public void onUpstreamDiscarded(EventTime eventTime, + MediaSourceEventListener.MediaLoadData mediaLoadData) { - @Override - public void onDownstreamFormatChanged(EventTime eventTime, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - if (mediaLoadData.trackFormat != null && mediaLoadData.trackFormat.containerMimeType != null) { - mimeType = mediaLoadData.trackFormat.containerMimeType; - } - } + } - @Override - public void onUpstreamDiscarded(EventTime eventTime, - MediaSourceEventListener.MediaLoadData mediaLoadData) { + @Override + public void onMediaPeriodCreated(EventTime eventTime) { - } + } - @Override - public void onMediaPeriodCreated(EventTime eventTime) { + @Override + public void onMediaPeriodReleased(EventTime eventTime) { - } + } - @Override - public void onMediaPeriodReleased(EventTime eventTime) { + @Override + public void onReadingStarted(EventTime eventTime) { - } + } - @Override - public void onReadingStarted(EventTime eventTime) { + @Override + public void onBandwidthEstimate(EventTime eventTime, int totalLoadTimeMs, + long totalBytesLoaded, long bitrateEstimate) { - } + } - @Override - public void onBandwidthEstimate(EventTime eventTime, int totalLoadTimeMs, - long totalBytesLoaded, long bitrateEstimate) { + @Override + public void onMetadata(EventTime eventTime, Metadata metadata) { - } + } - @Override - public void onMetadata(EventTime eventTime, Metadata metadata) { - - } + @Override + public void onDecoderEnabled(EventTime eventTime, int trackType, + DecoderCounters decoderCounters) { - @Override - public void onDecoderEnabled(EventTime eventTime, int trackType, - DecoderCounters decoderCounters) { + } - } + @Override + public void onDecoderInitialized(EventTime eventTime, int trackType, + String decoderName, long initializationDurationMs) { - @Override - public void onDecoderInitialized(EventTime eventTime, int trackType, - String decoderName, long initializationDurationMs) { + } + @Override + public void onDecoderInputFormatChanged(EventTime eventTime, int trackType, + Format format) { + if (trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_DEFAULT) { + handleRenditionChange(format); } + } - @Override - public void onDecoderInputFormatChanged(EventTime eventTime, int trackType, - Format format) { - if (trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_DEFAULT) { - handleRenditionChange(format); - } - } + @Override + public void onDecoderDisabled(EventTime eventTime, int trackType, + DecoderCounters decoderCounters) { - @Override - public void onDecoderDisabled(EventTime eventTime, int trackType, - DecoderCounters decoderCounters) { + } - } + @Override + public void onAudioSessionId(EventTime eventTime, int audioSessionId) { - @Override - public void onAudioSessionId(EventTime eventTime, int audioSessionId) { + } - } + @Override + public void onAudioUnderrun(EventTime eventTime, int bufferSize, + long bufferSizeMs, long elapsedSinceLastFeedMs) { - @Override - public void onAudioUnderrun(EventTime eventTime, int bufferSize, - long bufferSizeMs, long elapsedSinceLastFeedMs) { + } - } + @Override + public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, + long elapsedMs) { - @Override - public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, - long elapsedMs) { + } - } + @Override + public void onVideoSizeChanged(EventTime eventTime, int width, int height, + int unappliedRotationDegrees, float pixelWidthHeightRatio) { + sourceWidth = width; + sourceHeight = height; + } - @Override - public void onVideoSizeChanged(EventTime eventTime, int width, int height, - int unappliedRotationDegrees, float pixelWidthHeightRatio) { - sourceWidth = width; - sourceHeight = height; - } + @Override + public void onRenderedFirstFrame(EventTime eventTime, Surface surface) { - @Override - public void onRenderedFirstFrame(EventTime eventTime, Surface surface) { + } - } + @Override + public void onDrmKeysLoaded(EventTime eventTime) { - @Override - public void onDrmKeysLoaded(EventTime eventTime) { + } - } + @Override + public void onDrmSessionManagerError(EventTime eventTime, Exception e) { + internalError(new MuxErrorException(ERROR_DRM, "DrmSessionManagerError - " + e.getMessage())); + } - @Override - public void onDrmSessionManagerError(EventTime eventTime, Exception e) { - internalError(new MuxErrorException(ERROR_DRM, "DrmSessionManagerError - " + e.getMessage())); - } + @Override + public void onDrmKeysRestored(EventTime eventTime) { - @Override - public void onDrmKeysRestored(EventTime eventTime) { + } - } + @Override + public void onDrmKeysRemoved(EventTime eventTime) { - @Override - public void onDrmKeysRemoved(EventTime eventTime) { + } + @Override + public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { + if (timeline != null && timeline.getWindowCount() > 0) { + Timeline.Window window = new Timeline.Window(); + timeline.getWindow(0, window); + sourceDuration = window.getDurationMs(); } + } - @Override - public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { - if (timeline != null && timeline.getWindowCount() > 0) { - Timeline.Window window = new Timeline.Window(); - timeline.getWindow(0, window); - sourceDuration = window.getDurationMs(); - } - } + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + bandwidthDispatcher.onTracksChanged(trackGroups); + configurePlaybackHeadUpdateInterval(); + } - @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - bandwidthDispatcher.onTracksChanged(trackGroups); - configurePlaybackHeadUpdateInterval(); - } + @Override + public void onLoadingChanged(boolean isLoading) { - @Override - public void onLoadingChanged(boolean isLoading) { + } + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + PlayerState state = this.getState(); + if (state == PlayerState.PLAYING_ADS) { + // Ignore all normal events while playing ads + return; } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - PlayerState state = this.getState(); - if (state == PlayerState.PLAYING_ADS) { - // Ignore all normal events while playing ads - return; + switch (playbackState) { + case Player.STATE_BUFFERING: + // We have entered buffering + buffering(); + // If we are expected to playWhenReady, signal the play event + if (playWhenReady) { + play(); + } else if (state != PlayerState.PAUSED) { + pause(); } - switch (playbackState) { - case Player.STATE_BUFFERING: - // We have entered buffering - buffering(); - // If we are expected to playWhenReady, signal the play event - if (playWhenReady) { - play(); - } else if (state != PlayerState.PAUSED) { - pause(); - } - break; - case Player.STATE_ENDED: - ended(); - break; - case Player.STATE_READY: - // By the time we get here, it depends on playWhenReady to know if we're playing - if (playWhenReady) { - playing(); - } else if (state != PlayerState.PAUSED) { - pause(); - } - break; - case Player.STATE_IDLE: - default: - // don't care - break; + break; + case Player.STATE_ENDED: + ended(); + break; + case Player.STATE_READY: + // By the time we get here, it depends on playWhenReady to know if we're playing + if (playWhenReady) { + playing(); + } else if (state != PlayerState.PAUSED) { + pause(); } - } - - @Override - public void onRepeatModeChanged(int repeatMode) { - - } - - @Override - public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { - - } - - @Override - public void onPlayerError(ExoPlaybackException e) { - if (e.type == ExoPlaybackException.TYPE_RENDERER) { - Exception cause = e.getRendererException(); - if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { - MediaCodecRenderer.DecoderInitializationException die = (MediaCodecRenderer.DecoderInitializationException) cause; - if (die.codecInfo == null) { - if (die.getCause() instanceof MediaCodecUtil.DecoderQueryException) { - internalError(new MuxErrorException(e.type, "Unable to query device decoders")); - } else if (die.secureDecoderRequired) { - internalError(new MuxErrorException(e.type, "No secure decoder for " + die.mimeType)); - } else { - internalError(new MuxErrorException(e.type, "No decoder for " + die.mimeType)); - } - } else { - internalError(new MuxErrorException(e.type, "Unable to instantiate decoder for " + die.mimeType)); - } - } - else { - internalError(new MuxErrorException(e.type, cause.getClass().getCanonicalName() + " - " + cause.getMessage())); - } - } else if (e.type == ExoPlaybackException.TYPE_SOURCE) { - Exception error = e.getSourceException(); - internalError(new MuxErrorException(e.type, error.getClass().getCanonicalName() + " - " + error.getMessage())); - } else if (e.type == ExoPlaybackException.TYPE_UNEXPECTED) { - Exception error = e.getUnexpectedException(); - internalError(new MuxErrorException(e.type, error.getClass().getCanonicalName() + " - " + error.getMessage())); + break; + case Player.STATE_IDLE: + default: + // don't care + break; + } + } + + @Override + public void onRepeatModeChanged(int repeatMode) { + + } + + @Override + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { + + } + + @Override + public void onPlayerError(ExoPlaybackException e) { + if (e.type == ExoPlaybackException.TYPE_RENDERER) { + Exception cause = e.getRendererException(); + if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { + MediaCodecRenderer.DecoderInitializationException die = (MediaCodecRenderer.DecoderInitializationException) cause; + if (die.codecInfo == null) { + if (die.getCause() instanceof MediaCodecUtil.DecoderQueryException) { + internalError(new MuxErrorException(e.type, "Unable to query device decoders")); + } else if (die.secureDecoderRequired) { + internalError(new MuxErrorException(e.type, "No secure decoder for " + die.mimeType)); + } else { + internalError(new MuxErrorException(e.type, "No decoder for " + die.mimeType)); + } } else { - internalError(e); + internalError( + new MuxErrorException(e.type, "Unable to instantiate decoder for " + die.mimeType)); } + } else { + internalError(new MuxErrorException(e.type, + cause.getClass().getCanonicalName() + " - " + cause.getMessage())); + } + } else if (e.type == ExoPlaybackException.TYPE_SOURCE) { + Exception error = e.getSourceException(); + internalError(new MuxErrorException(e.type, + error.getClass().getCanonicalName() + " - " + error.getMessage())); + } else if (e.type == ExoPlaybackException.TYPE_UNEXPECTED) { + Exception error = e.getUnexpectedException(); + internalError(new MuxErrorException(e.type, + error.getClass().getCanonicalName() + " - " + error.getMessage())); + } else { + internalError(e); } + } - @Override - public void onPositionDiscontinuity(int reason) { - if (reason == Player.DISCONTINUITY_REASON_SEEK) { - if ( state == PlayerState.PAUSED || !playItemHaveVideoTrack ) { - seeked(false); - } - } + @Override + public void onPositionDiscontinuity(int reason) { + if (reason == Player.DISCONTINUITY_REASON_SEEK) { + if (state == PlayerState.PAUSED || !playItemHaveVideoTrack) { + seeked(false); + } } + } - @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - } + } - @Override - public void onSeekProcessed() { - } + @Override + public void onSeekProcessed() { + } - @Override - public void onSurfaceSizeChanged(AnalyticsListener.EventTime eventTime, int width, int height) { + @Override + public void onSurfaceSizeChanged(AnalyticsListener.EventTime eventTime, int width, int height) { - } + } - @Override - public void onIsPlayingChanged(AnalyticsListener.EventTime eventTime, boolean isPlaying) { + @Override + public void onIsPlayingChanged(AnalyticsListener.EventTime eventTime, boolean isPlaying) { - } + } - @Override - public void onAudioAttributesChanged(AnalyticsListener.EventTime eventTime, AudioAttributes audioAttributes) { + @Override + public void onAudioAttributesChanged(AnalyticsListener.EventTime eventTime, + AudioAttributes audioAttributes) { - } + } - @Override - public void onPlaybackSuppressionReasonChanged(AnalyticsListener.EventTime eventTime, int playbackSuppressionReason) { + @Override + public void onPlaybackSuppressionReasonChanged(AnalyticsListener.EventTime eventTime, + int playbackSuppressionReason) { - } + } - @Override - public void onVolumeChanged(AnalyticsListener.EventTime eventTime, float volume) { + @Override + public void onVolumeChanged(AnalyticsListener.EventTime eventTime, float volume) { - } + } } diff --git a/MuxExoPlayer/src/r2_12_1/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java b/MuxExoPlayer/src/r2_12_1/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java index c849d49c..2bf03356 100644 --- a/MuxExoPlayer/src/r2_12_1/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java +++ b/MuxExoPlayer/src/r2_12_1/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java @@ -2,7 +2,6 @@ import android.content.Context; import android.view.Surface; - import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; @@ -22,362 +21,381 @@ import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; import com.mux.stats.sdk.core.model.CustomerViewData; - import java.io.IOException; -public class MuxStatsExoPlayer extends MuxBaseExoPlayer implements AnalyticsListener, Player.EventListener{ - - static final String TAG = "MuxStatsEventQueue"; - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData) { - this(ctx, player, playerName, customerPlayerData, - customerVideoData, null, true); +public class MuxStatsExoPlayer extends MuxBaseExoPlayer implements AnalyticsListener, + Player.EventListener { + + static final String TAG = "MuxStatsEventQueue"; + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData) { + this(ctx, player, playerName, customerPlayerData, + customerVideoData, null, true); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, + customerViewData, true); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + boolean sentryEnabled) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, + null, sentryEnabled); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData, boolean sentryEnabled) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, + customerViewData, sentryEnabled, new MuxNetworkRequests()); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData, boolean sentryEnabled, + INetworkRequest networkRequests) { + super(ctx, player, playerName, customerPlayerData, customerVideoData, + customerViewData, sentryEnabled, networkRequests); + + if (player instanceof SimpleExoPlayer) { + ((SimpleExoPlayer) player).addAnalyticsListener(this); + } else { + player.addListener(this); } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, - customerViewData, true); + if (player.getPlaybackState() == Player.STATE_BUFFERING) { + // playback started before muxStats was initialized + play(); + buffering(); + } else if (player.getPlaybackState() == Player.STATE_READY) { + // We have to simulate all the events we expect to see here, even though not ideal + play(); + buffering(); + playing(); } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - boolean sentryEnabled) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, - null, sentryEnabled); + } + + @Override + public void release() { + if (this.player != null && this.player.get() != null) { + ExoPlayer player = this.player.get(); + if (player instanceof SimpleExoPlayer) { + ((SimpleExoPlayer) player).removeAnalyticsListener(this); + } else { + player.removeListener(this); + } } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData, boolean sentryEnabled) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, - customerViewData, sentryEnabled, new MuxNetworkRequests()); + super.release(); + } + + // ------BEGIN AnalyticsListener callbacks------ + @Override + public void onAudioAttributesChanged(AnalyticsListener.EventTime eventTime, + AudioAttributes audioAttributes) { + } + + @Override + public void onAudioSessionId(AnalyticsListener.EventTime eventTime, int audioSessionId) { + } + + @Override + public void onAudioUnderrun(AnalyticsListener.EventTime eventTime, int bufferSize, + long bufferSizeMs, long elapsedSinceLastFeedMs) { + } + + @Override + public void onVideoInputFormatChanged(EventTime eventTime, Format format) { + handleRenditionChange(format); + } + + @Override + public void onDownstreamFormatChanged(AnalyticsListener.EventTime eventTime, + MediaLoadData mediaLoadData) { + if (mediaLoadData.trackFormat != null && mediaLoadData.trackFormat.containerMimeType != null) { + mimeType = mediaLoadData.trackFormat.containerMimeType; } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData, boolean sentryEnabled, - INetworkRequest networkRequests ) { - super(ctx, player, playerName, customerPlayerData, customerVideoData, - customerViewData, sentryEnabled, networkRequests); - - if (player instanceof SimpleExoPlayer) { - ((SimpleExoPlayer) player).addAnalyticsListener(this); - } else { - player.addListener(this); - } - if (player.getPlaybackState() == Player.STATE_BUFFERING) { - // playback started before muxStats was initialized - play(); - buffering(); - } else if (player.getPlaybackState() == Player.STATE_READY) { - // We have to simulate all the events we expect to see here, even though not ideal - play(); - buffering(); - playing(); - } + } + + @Override + public void onDrmKeysLoaded(AnalyticsListener.EventTime eventTime) { + } + + @Override + public void onDrmKeysRemoved(AnalyticsListener.EventTime eventTime) { + } + + @Override + public void onDrmKeysRestored(AnalyticsListener.EventTime eventTime) { + } + + @Override + public void onDrmSessionManagerError(AnalyticsListener.EventTime eventTime, Exception e) { + internalError(new MuxErrorException(ERROR_DRM, "DrmSessionManagerError - " + e.getMessage())); + } + + // Note: onLoadingChanged was deprecated and moved to onIsLoadingChanged in 2.12.0 + @Override + public void onIsLoadingChanged(AnalyticsListener.EventTime eventTime, boolean isLoading) { + onIsLoadingChanged(isLoading); + } + + @Override + public void onIsPlayingChanged(AnalyticsListener.EventTime eventTime, boolean isPlaying) { + } + + @Override + public void onLoadCanceled(AnalyticsListener.EventTime eventTime, + LoadEventInfo loadEventInfo, + MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadCanceled(loadEventInfo.dataSpec); + } + + @Override + public void onLoadCompleted(AnalyticsListener.EventTime eventTime, + LoadEventInfo loadEventInfo, + MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadCompleted(loadEventInfo.dataSpec, mediaLoadData.dataType, + mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, + mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs, + loadEventInfo.loadDurationMs, loadEventInfo.bytesLoaded, + loadEventInfo.responseHeaders); + } + + @Override + public void onLoadError(AnalyticsListener.EventTime eventTime, + LoadEventInfo loadEventInfo, + MediaLoadData mediaLoadData, IOException e, + boolean wasCanceled) { + bandwidthDispatcher.onLoadError(loadEventInfo.dataSpec, mediaLoadData.dataType, e); + } + + @Override + public void onLoadStarted(AnalyticsListener.EventTime eventTime, + LoadEventInfo loadEventInfo, + MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadStarted(loadEventInfo.dataSpec, mediaLoadData.dataType, + mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, + mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs); + } + + @Override + public void onMetadata(AnalyticsListener.EventTime eventTime, Metadata metadata) { + } + + @Override + public void onPlaybackParametersChanged(AnalyticsListener.EventTime eventTime, + PlaybackParameters playbackParameters) { + onPlaybackParametersChanged(playbackParameters); + } + + @Override + public void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) { + onPlaybackStateChanged(state); + } + + @Override + public void onPlaybackSuppressionReasonChanged(AnalyticsListener.EventTime eventTime, + int playbackSuppressionReason) { + } + + @Override + public void onPlayerError(AnalyticsListener.EventTime eventTime, ExoPlaybackException error) { + onPlayerError(error); + } + + @Override + public void onPlayWhenReadyChanged(AnalyticsListener.EventTime eventTime, boolean playWhenReady, + int reason) { + onPlayWhenReadyChanged(playWhenReady, reason); + onPlaybackStateChanged(player.get().getPlaybackState()); + } + + @Override + public void onPositionDiscontinuity(AnalyticsListener.EventTime eventTime, int reason) { + onPositionDiscontinuity(reason); + } + + @Override + public void onRenderedFirstFrame(AnalyticsListener.EventTime eventTime, Surface surface) { + } + + @Override + public void onRepeatModeChanged(AnalyticsListener.EventTime eventTime, int repeatMode) { + onRepeatModeChanged(repeatMode); + } + + // Note: onSeekProcessed was deprecated in 2.12.0 + + @Override + public void onSeekStarted(AnalyticsListener.EventTime eventTime) { + seeking(); + } + + @Override + public void onShuffleModeChanged(AnalyticsListener.EventTime eventTime, + boolean shuffleModeEnabled) { + onShuffleModeEnabledChanged(shuffleModeEnabled); + } + + @Override + public void onSurfaceSizeChanged(AnalyticsListener.EventTime eventTime, int width, + int height) { + } + + @Override + public void onTimelineChanged(AnalyticsListener.EventTime eventTime, int reason) { + onTimelineChanged(eventTime.timeline, reason); + } + + @Override + public void onTracksChanged(AnalyticsListener.EventTime eventTime, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections) { + onTracksChanged(trackGroups, trackSelections); + } + + @Override + public void onUpstreamDiscarded(EventTime eventTime, MediaLoadData mediaLoadData) { + } + + @Override + public void onVideoSizeChanged(AnalyticsListener.EventTime eventTime, int width, int height, + int unappliedRotationDegrees, float pixelWidthHeightRatio) { + sourceWidth = width; + sourceHeight = height; + } + + @Override + public void onVolumeChanged(AnalyticsListener.EventTime eventTime, float volume) { + } + // ------END AnalyticsListener callbacks------ + + // ------BEGIN Player.EventListener callbacks------ + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + } + + @Override + public void onPlaybackStateChanged(int playbackState) { + /* + * Sometimes onPlaybackStateChanged callback will not be triggered, and it is + * prone to bugs to keep same value in two places, so we should always access + * playWhenReady via the player object. + */ + boolean playWhenReady = player.get().getPlayWhenReady(); + PlayerState state = this.getState(); + if (state == PlayerState.PLAYING_ADS) { + // Ignore all normal events while playing ads + return; } - - @Override - public void release() { - if (this.player != null && this.player.get() != null) { - ExoPlayer player = this.player.get(); - if (player instanceof SimpleExoPlayer) { - ((SimpleExoPlayer) player).removeAnalyticsListener(this); - } else { - player.removeListener(this); - } + switch (playbackState) { + case Player.STATE_BUFFERING: + // We have entered buffering + buffering(); + // If we are expected to playWhenReady, signal the play event + if (playWhenReady) { + play(); + } else if (state != PlayerState.PAUSED) { + pause(); } - super.release(); - } - - // ------BEGIN AnalyticsListener callbacks------ - @Override - public void onAudioAttributesChanged(AnalyticsListener.EventTime eventTime, - AudioAttributes audioAttributes) { } - - @Override - public void onAudioSessionId(AnalyticsListener.EventTime eventTime, int audioSessionId) { } - - @Override - public void onAudioUnderrun(AnalyticsListener.EventTime eventTime, int bufferSize, - long bufferSizeMs, long elapsedSinceLastFeedMs) { } - - @Override - public void onVideoInputFormatChanged(EventTime eventTime, Format format) { - handleRenditionChange(format); - } - - @Override - public void onDownstreamFormatChanged(AnalyticsListener.EventTime eventTime, - MediaLoadData mediaLoadData) { - if (mediaLoadData.trackFormat != null && mediaLoadData.trackFormat.containerMimeType != null) { - mimeType = mediaLoadData.trackFormat.containerMimeType; + break; + case Player.STATE_ENDED: + ended(); + break; + case Player.STATE_READY: + // By the time we get here, it depends on playWhenReady to know if we're playing + if (playWhenReady) { + playing(); + } else if (state != PlayerState.PAUSED) { + pause(); } + break; + case Player.STATE_IDLE: + default: + // don't care + break; } - - @Override - public void onDrmKeysLoaded(AnalyticsListener.EventTime eventTime) { } - - @Override - public void onDrmKeysRemoved(AnalyticsListener.EventTime eventTime) { } - - @Override - public void onDrmKeysRestored(AnalyticsListener.EventTime eventTime) { } - - @Override - public void onDrmSessionManagerError(AnalyticsListener.EventTime eventTime, Exception e) { - internalError(new MuxErrorException(ERROR_DRM, "DrmSessionManagerError - " + e.getMessage())); - } - - // Note: onLoadingChanged was deprecated and moved to onIsLoadingChanged in 2.12.0 - @Override - public void onIsLoadingChanged(AnalyticsListener.EventTime eventTime, boolean isLoading) { - onIsLoadingChanged(isLoading); - } - - @Override - public void onIsPlayingChanged(AnalyticsListener.EventTime eventTime, boolean isPlaying) { } - - @Override - public void onLoadCanceled(AnalyticsListener.EventTime eventTime, - LoadEventInfo loadEventInfo, - MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadCanceled(loadEventInfo.dataSpec); - } - - @Override - public void onLoadCompleted(AnalyticsListener.EventTime eventTime, - LoadEventInfo loadEventInfo, - MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadCompleted(loadEventInfo.dataSpec, mediaLoadData.dataType, - mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, - mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs, - loadEventInfo.loadDurationMs, loadEventInfo.bytesLoaded, - loadEventInfo.responseHeaders); - } - - @Override - public void onLoadError(AnalyticsListener.EventTime eventTime, - LoadEventInfo loadEventInfo, - MediaLoadData mediaLoadData, IOException e, - boolean wasCanceled) { - bandwidthDispatcher.onLoadError(loadEventInfo.dataSpec, mediaLoadData.dataType, e); - } - - @Override - public void onLoadStarted(AnalyticsListener.EventTime eventTime, - LoadEventInfo loadEventInfo, - MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadStarted(loadEventInfo.dataSpec, mediaLoadData.dataType, - mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, - mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs); - } - - @Override - public void onMetadata(AnalyticsListener.EventTime eventTime, Metadata metadata) { } - - @Override - public void onPlaybackParametersChanged(AnalyticsListener.EventTime eventTime, - PlaybackParameters playbackParameters) { - onPlaybackParametersChanged(playbackParameters); - } - - @Override - public void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) { - onPlaybackStateChanged(state); - } - - @Override - public void onPlaybackSuppressionReasonChanged(AnalyticsListener.EventTime eventTime, - int playbackSuppressionReason) { } - - @Override - public void onPlayerError(AnalyticsListener.EventTime eventTime, ExoPlaybackException error) { - onPlayerError(error); - } - - @Override - public void onPlayWhenReadyChanged(AnalyticsListener.EventTime eventTime, boolean playWhenReady, - int reason) { - onPlayWhenReadyChanged(playWhenReady, reason); - onPlaybackStateChanged(player.get().getPlaybackState()); - } - - @Override - public void onPositionDiscontinuity(AnalyticsListener.EventTime eventTime, int reason) { - onPositionDiscontinuity(reason); - } - - @Override - public void onRenderedFirstFrame(AnalyticsListener.EventTime eventTime, Surface surface) { } - - @Override - public void onRepeatModeChanged(AnalyticsListener.EventTime eventTime, int repeatMode) { - onRepeatModeChanged(repeatMode); - } - - // Note: onSeekProcessed was deprecated in 2.12.0 - - @Override - public void onSeekStarted(AnalyticsListener.EventTime eventTime) { - seeking(); - } - - @Override - public void onShuffleModeChanged(AnalyticsListener.EventTime eventTime, - boolean shuffleModeEnabled) { - onShuffleModeEnabledChanged(shuffleModeEnabled); - } - - @Override - public void onSurfaceSizeChanged(AnalyticsListener.EventTime eventTime, int width, - int height) { } - - @Override - public void onTimelineChanged(AnalyticsListener.EventTime eventTime, int reason) { - onTimelineChanged(eventTime.timeline, reason); - } - - @Override - public void onTracksChanged(AnalyticsListener.EventTime eventTime, TrackGroupArray trackGroups, - TrackSelectionArray trackSelections) { - onTracksChanged(trackGroups, trackSelections); - } - - @Override - public void onUpstreamDiscarded(EventTime eventTime, MediaLoadData mediaLoadData) { } - - @Override - public void onVideoSizeChanged(AnalyticsListener.EventTime eventTime, int width, int height, - int unappliedRotationDegrees, float pixelWidthHeightRatio) { - sourceWidth = width; - sourceHeight = height; - } - - @Override - public void onVolumeChanged(AnalyticsListener.EventTime eventTime, float volume) { } - // ------END AnalyticsListener callbacks------ - - // ------BEGIN Player.EventListener callbacks------ - @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { } - - @Override - public void onPlaybackStateChanged(int playbackState) { - /* - * Sometimes onPlaybackStateChanged callback will not be triggered, and it is - * prone to bugs to keep same value in two places, so we should always access - * playWhenReady via the player object. - */ - boolean playWhenReady = player.get().getPlayWhenReady(); - PlayerState state = this.getState(); - if (state == PlayerState.PLAYING_ADS) { - // Ignore all normal events while playing ads - return; - } - switch (playbackState) { - case Player.STATE_BUFFERING: - // We have entered buffering - buffering(); - // If we are expected to playWhenReady, signal the play event - if (playWhenReady) { - play(); - } else if (state != PlayerState.PAUSED) { - pause(); - } - break; - case Player.STATE_ENDED: - ended(); - break; - case Player.STATE_READY: - // By the time we get here, it depends on playWhenReady to know if we're playing - if (playWhenReady) { - playing(); - } else if (state != PlayerState.PAUSED) { - pause(); - } - break; - case Player.STATE_IDLE: - default: - // don't care - break; - } - } - - @Override - public void onPlayerError(ExoPlaybackException e) { - if (e.type == ExoPlaybackException.TYPE_RENDERER) { - Exception cause = e.getRendererException(); - if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { - MediaCodecRenderer.DecoderInitializationException die = (MediaCodecRenderer.DecoderInitializationException) cause; - if (die.codecInfo == null) { - if (die.getCause() instanceof MediaCodecUtil.DecoderQueryException) { - internalError(new MuxErrorException(e.type, "Unable to query device decoders")); - } else if (die.secureDecoderRequired) { - internalError(new MuxErrorException(e.type, "No secure decoder for " + die.mimeType)); - } else { - internalError(new MuxErrorException(e.type, "No decoder for " + die.mimeType)); - } - } else { - internalError(new MuxErrorException(e.type, "Unable to instantiate decoder for " + die.mimeType)); - } - } - else { - internalError(new MuxErrorException(e.type, cause.getClass().getCanonicalName() + " - " + cause.getMessage())); - } - } else if (e.type == ExoPlaybackException.TYPE_SOURCE) { - Exception error = e.getSourceException(); - internalError(new MuxErrorException(e.type, error.getClass().getCanonicalName() + " - " + error.getMessage())); - } else if (e.type == ExoPlaybackException.TYPE_UNEXPECTED) { - Exception error = e.getUnexpectedException(); - internalError(new MuxErrorException(e.type, error.getClass().getCanonicalName() + " - " + error.getMessage())); + } + + @Override + public void onPlayerError(ExoPlaybackException e) { + if (e.type == ExoPlaybackException.TYPE_RENDERER) { + Exception cause = e.getRendererException(); + if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { + MediaCodecRenderer.DecoderInitializationException die = (MediaCodecRenderer.DecoderInitializationException) cause; + if (die.codecInfo == null) { + if (die.getCause() instanceof MediaCodecUtil.DecoderQueryException) { + internalError(new MuxErrorException(e.type, "Unable to query device decoders")); + } else if (die.secureDecoderRequired) { + internalError(new MuxErrorException(e.type, "No secure decoder for " + die.mimeType)); + } else { + internalError(new MuxErrorException(e.type, "No decoder for " + die.mimeType)); + } } else { - internalError(e); + internalError( + new MuxErrorException(e.type, "Unable to instantiate decoder for " + die.mimeType)); } + } else { + internalError(new MuxErrorException(e.type, + cause.getClass().getCanonicalName() + " - " + cause.getMessage())); + } + } else if (e.type == ExoPlaybackException.TYPE_SOURCE) { + Exception error = e.getSourceException(); + internalError(new MuxErrorException(e.type, + error.getClass().getCanonicalName() + " - " + error.getMessage())); + } else if (e.type == ExoPlaybackException.TYPE_UNEXPECTED) { + Exception error = e.getUnexpectedException(); + internalError(new MuxErrorException(e.type, + error.getClass().getCanonicalName() + " - " + error.getMessage())); + } else { + internalError(e); } - - @Override - public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { - // Nothing to do here + } + + @Override + public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { + // Nothing to do here + } + + @Override + public void onPositionDiscontinuity(int reason) { + if (reason == Player.DISCONTINUITY_REASON_SEEK) { + if (state == PlayerState.PAUSED || !playItemHaveVideoTrack) { + seeked(false); + } } + } - @Override - public void onPositionDiscontinuity(int reason) { - if (reason == Player.DISCONTINUITY_REASON_SEEK) { - if ( state == PlayerState.PAUSED || !playItemHaveVideoTrack ) { - seeked(false); - } - } - } - - @Override - public void onRepeatModeChanged(int repeatMode) { } + @Override + public void onRepeatModeChanged(int repeatMode) { + } - // Note, onSeekProcessed was deprecated in 2.12.0 + // Note, onSeekProcessed was deprecated in 2.12.0 - @Override - public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { } + @Override + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { + } - @Override - public void onTimelineChanged(Timeline timeline, int reason) { - if (timeline != null && timeline.getWindowCount() > 0) { - Timeline.Window window = new Timeline.Window(); - timeline.getWindow(0, window); - sourceDuration = window.getDurationMs(); - } + @Override + public void onTimelineChanged(Timeline timeline, int reason) { + if (timeline != null && timeline.getWindowCount() > 0) { + Timeline.Window window = new Timeline.Window(); + timeline.getWindow(0, window); + sourceDuration = window.getDurationMs(); } + } - @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - bandwidthDispatcher.onTracksChanged(trackGroups); - configurePlaybackHeadUpdateInterval(); - } + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + bandwidthDispatcher.onTracksChanged(trackGroups); + configurePlaybackHeadUpdateInterval(); + } } diff --git a/MuxExoPlayer/src/r2_9_6/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java b/MuxExoPlayer/src/r2_9_6/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java index f131398c..fae8f20c 100644 --- a/MuxExoPlayer/src/r2_9_6/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java +++ b/MuxExoPlayer/src/r2_9_6/java/com/mux/stats/sdk/muxstats/MuxStatsExoPlayer.java @@ -2,7 +2,6 @@ import android.content.Context; import android.view.Surface; - import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; @@ -22,398 +21,401 @@ import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; import com.mux.stats.sdk.core.model.CustomerViewData; - import java.io.IOException; -public class MuxStatsExoPlayer extends MuxBaseExoPlayer implements AnalyticsListener, Player.EventListener{ - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, null, true); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, true); - } +public class MuxStatsExoPlayer extends MuxBaseExoPlayer implements AnalyticsListener, + Player.EventListener { + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, null, true); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, true); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + boolean sentryEnabled) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, null, sentryEnabled); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData, boolean sentryEnabled) { + this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, + sentryEnabled, new MuxNetworkRequests()); + } + + public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, + CustomerPlayerData customerPlayerData, + CustomerVideoData customerVideoData, + CustomerViewData customerViewData, boolean sentryEnabled, + INetworkRequest networkRequest) { + super(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, + sentryEnabled, networkRequest); + if (player instanceof SimpleExoPlayer) { + ((SimpleExoPlayer) player).addAnalyticsListener(this); + } else { + player.addListener(this); + } + if (player.getPlaybackState() == Player.STATE_BUFFERING) { + // playback started before muxStats was initialized + play(); + buffering(); + } else if (player.getPlaybackState() == Player.STATE_READY) { + // We have to simulate all the events we expect to see here, even though not ideal + play(); + buffering(); + playing(); + } + } + + @Override + public void release() { + if (player != null && this.player.get() != null) { + ExoPlayer player = this.player.get(); + if (player instanceof SimpleExoPlayer) { + ((SimpleExoPlayer) player).removeAnalyticsListener(this); + } else { + player.removeListener(this); + } + } + super.release(); + } + + @Override + public void onPlayerStateChanged(EventTime eventTime, boolean playWhenReady, + int playbackState) { + onPlayerStateChanged(playWhenReady, playbackState); + } + + @Override + public void onTimelineChanged(EventTime eventTime, int reason) { + onTimelineChanged(eventTime.timeline, null, reason); + } + + @Override + public void onPositionDiscontinuity(EventTime eventTime, int reason) { + onPositionDiscontinuity(reason); + } + + @Override + public void onSeekStarted(EventTime eventTime) { + seeking(); + } + + @Override + public void onSeekProcessed(EventTime eventTime) { + onSeekProcessed(); + } + + @Override + public void onPlaybackParametersChanged(EventTime eventTime, + PlaybackParameters playbackParameters) { + onPlaybackParametersChanged(playbackParameters); + } + + @Override + public void onRepeatModeChanged(EventTime eventTime, int repeatMode) { + onRepeatModeChanged(repeatMode); + } + + @Override + public void onShuffleModeChanged(EventTime eventTime, + boolean shuffleModeEnabled) { + onShuffleModeEnabledChanged(shuffleModeEnabled); + } + + @Override + public void onLoadingChanged(EventTime eventTime, boolean isLoading) { + onLoadingChanged(isLoading); + } + + @Override + public void onPlayerError(EventTime eventTime, ExoPlaybackException error) { + onPlayerError(error); + } + + @Override + public void onTracksChanged(EventTime eventTime, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections) { + onTracksChanged(trackGroups, trackSelections); + } + + @Override + public void onLoadStarted(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadStarted(loadEventInfo.dataSpec, mediaLoadData.dataType, + mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, + mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs); + } + + @Override + public void onLoadCompleted(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadCompleted(loadEventInfo.dataSpec, mediaLoadData.dataType, + mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, + mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs, + loadEventInfo.loadDurationMs, loadEventInfo.bytesLoaded, + loadEventInfo.responseHeaders); + } + + @Override + public void onLoadCanceled(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + bandwidthDispatcher.onLoadCanceled(loadEventInfo.dataSpec); + } + + @Override + public void onLoadError(EventTime eventTime, + MediaSourceEventListener.LoadEventInfo loadEventInfo, + MediaSourceEventListener.MediaLoadData mediaLoadData, IOException e, + boolean wasCanceled) { + bandwidthDispatcher.onLoadError(loadEventInfo.dataSpec, mediaLoadData.dataType, e); + } - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - boolean sentryEnabled) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, null, sentryEnabled); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData, boolean sentryEnabled) { - this(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, - sentryEnabled, new MuxNetworkRequests()); - } - - public MuxStatsExoPlayer(Context ctx, ExoPlayer player, String playerName, - CustomerPlayerData customerPlayerData, - CustomerVideoData customerVideoData, - CustomerViewData customerViewData, boolean sentryEnabled, - INetworkRequest networkRequest) { - super(ctx, player, playerName, customerPlayerData, customerVideoData, customerViewData, - sentryEnabled, networkRequest); - if (player instanceof SimpleExoPlayer) { - ((SimpleExoPlayer) player).addAnalyticsListener(this); - } else { - player.addListener(this); - } - if (player.getPlaybackState() == Player.STATE_BUFFERING) { - // playback started before muxStats was initialized - play(); - buffering(); - } else if (player.getPlaybackState() == Player.STATE_READY) { - // We have to simulate all the events we expect to see here, even though not ideal - play(); - buffering(); - playing(); - } + @Override + public void onDownstreamFormatChanged(EventTime eventTime, + MediaSourceEventListener.MediaLoadData mediaLoadData) { + if (mediaLoadData.trackFormat != null && mediaLoadData.trackFormat.containerMimeType != null) { + mimeType = mediaLoadData.trackFormat.containerMimeType; } + } - @Override - public void release() { - if (player != null && this.player.get() != null) { - ExoPlayer player = this.player.get(); - if (player instanceof SimpleExoPlayer) { - ((SimpleExoPlayer) player).removeAnalyticsListener(this); - } else { - player.removeListener(this); - } - } - super.release(); - } + @Override + public void onUpstreamDiscarded(EventTime eventTime, + MediaSourceEventListener.MediaLoadData mediaLoadData) { - @Override - public void onPlayerStateChanged(EventTime eventTime, boolean playWhenReady, - int playbackState) { - onPlayerStateChanged(playWhenReady, playbackState); - } + } - @Override - public void onTimelineChanged(EventTime eventTime, int reason) { - onTimelineChanged(eventTime.timeline, null, reason); - } + @Override + public void onMediaPeriodCreated(EventTime eventTime) { - @Override - public void onPositionDiscontinuity(EventTime eventTime, int reason) { - onPositionDiscontinuity(reason); - } + } - @Override - public void onSeekStarted(EventTime eventTime) { - seeking(); - } + @Override + public void onMediaPeriodReleased(EventTime eventTime) { - @Override - public void onSeekProcessed(EventTime eventTime) { - onSeekProcessed(); - } + } - @Override - public void onPlaybackParametersChanged(EventTime eventTime, - PlaybackParameters playbackParameters) { - onPlaybackParametersChanged(playbackParameters); - } + @Override + public void onReadingStarted(EventTime eventTime) { - @Override - public void onRepeatModeChanged(EventTime eventTime, int repeatMode) { - onRepeatModeChanged(repeatMode); - } + } - @Override - public void onShuffleModeChanged(EventTime eventTime, - boolean shuffleModeEnabled) { - onShuffleModeEnabledChanged(shuffleModeEnabled); - } + @Override + public void onBandwidthEstimate(EventTime eventTime, int totalLoadTimeMs, + long totalBytesLoaded, long bitrateEstimate) { - @Override - public void onLoadingChanged(EventTime eventTime, boolean isLoading) { - onLoadingChanged(isLoading); - } + } - @Override - public void onPlayerError(EventTime eventTime, ExoPlaybackException error) { - onPlayerError(error); - } + @Override + public void onMetadata(EventTime eventTime, Metadata metadata) { - @Override - public void onTracksChanged(EventTime eventTime, TrackGroupArray trackGroups, - TrackSelectionArray trackSelections) { - onTracksChanged(trackGroups, trackSelections); - } + } - @Override - public void onLoadStarted(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadStarted(loadEventInfo.dataSpec, mediaLoadData.dataType, - mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, - mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs); - } + @Override + public void onDecoderEnabled(EventTime eventTime, int trackType, + DecoderCounters decoderCounters) { - @Override - public void onLoadCompleted(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadCompleted(loadEventInfo.dataSpec, mediaLoadData.dataType, - mediaLoadData.trackFormat, mediaLoadData.mediaStartTimeMs, - mediaLoadData.mediaEndTimeMs, loadEventInfo.elapsedRealtimeMs, - loadEventInfo.loadDurationMs, loadEventInfo.bytesLoaded, - loadEventInfo.responseHeaders); - } + } - @Override - public void onLoadCanceled(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - bandwidthDispatcher.onLoadCanceled(loadEventInfo.dataSpec); - } + @Override + public void onDecoderInitialized(EventTime eventTime, int trackType, + String decoderName, long initializationDurationMs) { - @Override - public void onLoadError(EventTime eventTime, - MediaSourceEventListener.LoadEventInfo loadEventInfo, - MediaSourceEventListener.MediaLoadData mediaLoadData, IOException e, - boolean wasCanceled) { - bandwidthDispatcher.onLoadError(loadEventInfo.dataSpec, mediaLoadData.dataType, e); - } + } - @Override - public void onDownstreamFormatChanged(EventTime eventTime, - MediaSourceEventListener.MediaLoadData mediaLoadData) { - if (mediaLoadData.trackFormat != null && mediaLoadData.trackFormat.containerMimeType != null) { - mimeType = mediaLoadData.trackFormat.containerMimeType; - } + @Override + public void onDecoderInputFormatChanged(EventTime eventTime, int trackType, + Format format) { + if (trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_DEFAULT) { + handleRenditionChange(format); } + } - @Override - public void onUpstreamDiscarded(EventTime eventTime, - MediaSourceEventListener.MediaLoadData mediaLoadData) { + @Override + public void onDecoderDisabled(EventTime eventTime, int trackType, + DecoderCounters decoderCounters) { - } + } - @Override - public void onMediaPeriodCreated(EventTime eventTime) { + @Override + public void onAudioSessionId(EventTime eventTime, int audioSessionId) { - } + } - @Override - public void onMediaPeriodReleased(EventTime eventTime) { + @Override + public void onAudioUnderrun(EventTime eventTime, int bufferSize, + long bufferSizeMs, long elapsedSinceLastFeedMs) { - } + } - @Override - public void onReadingStarted(EventTime eventTime) { + @Override + public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, + long elapsedMs) { - } + } - @Override - public void onBandwidthEstimate(EventTime eventTime, int totalLoadTimeMs, - long totalBytesLoaded, long bitrateEstimate) { + @Override + public void onVideoSizeChanged(EventTime eventTime, int width, int height, + int unappliedRotationDegrees, float pixelWidthHeightRatio) { + sourceWidth = width; + sourceHeight = height; + } - } + @Override + public void onRenderedFirstFrame(EventTime eventTime, Surface surface) { - @Override - public void onMetadata(EventTime eventTime, Metadata metadata) { + } - } + @Override + public void onDrmKeysLoaded(EventTime eventTime) { - @Override - public void onDecoderEnabled(EventTime eventTime, int trackType, - DecoderCounters decoderCounters) { + } - } + @Override + public void onDrmSessionManagerError(EventTime eventTime, Exception e) { + internalError(new MuxErrorException(ERROR_DRM, "DrmSessionManagerError - " + e.getMessage())); + } - @Override - public void onDecoderInitialized(EventTime eventTime, int trackType, - String decoderName, long initializationDurationMs) { + @Override + public void onDrmKeysRestored(EventTime eventTime) { - } + } - @Override - public void onDecoderInputFormatChanged(EventTime eventTime, int trackType, - Format format) { - if (trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_DEFAULT) { - handleRenditionChange(format); - } - } + @Override + public void onDrmKeysRemoved(EventTime eventTime) { - @Override - public void onDecoderDisabled(EventTime eventTime, int trackType, - DecoderCounters decoderCounters) { + } + @Override + public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { + if (timeline != null && timeline.getWindowCount() > 0) { + Timeline.Window window = new Timeline.Window(); + timeline.getWindow(0, window); + sourceDuration = window.getDurationMs(); } + } - @Override - public void onAudioSessionId(EventTime eventTime, int audioSessionId) { + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + bandwidthDispatcher.onTracksChanged(trackGroups); + configurePlaybackHeadUpdateInterval(); + } - } + @Override + public void onLoadingChanged(boolean isLoading) { - @Override - public void onAudioUnderrun(EventTime eventTime, int bufferSize, - long bufferSizeMs, long elapsedSinceLastFeedMs) { + } + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + PlayerState state = this.getState(); + if (state == PlayerState.PLAYING_ADS) { + // Ignore all normal events while playing ads + return; } - - @Override - public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, - long elapsedMs) { - - } - - @Override - public void onVideoSizeChanged(EventTime eventTime, int width, int height, - int unappliedRotationDegrees, float pixelWidthHeightRatio) { - sourceWidth = width; - sourceHeight = height; - } - - @Override - public void onRenderedFirstFrame(EventTime eventTime, Surface surface) { - - } - - @Override - public void onDrmKeysLoaded(EventTime eventTime) { - - } - - @Override - public void onDrmSessionManagerError(EventTime eventTime, Exception e) { - internalError(new MuxErrorException(ERROR_DRM, "DrmSessionManagerError - " + e.getMessage())); - } - - @Override - public void onDrmKeysRestored(EventTime eventTime) { - - } - - @Override - public void onDrmKeysRemoved(EventTime eventTime) { - - } - - @Override - public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { - if (timeline != null && timeline.getWindowCount() > 0) { - Timeline.Window window = new Timeline.Window(); - timeline.getWindow(0, window); - sourceDuration = window.getDurationMs(); + switch (playbackState) { + case Player.STATE_BUFFERING: + // We have entered buffering + buffering(); + // If we are expected to playWhenReady, signal the play event + if (playWhenReady) { + play(); + } else if (state != PlayerState.PAUSED) { + pause(); } - } - - @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - bandwidthDispatcher.onTracksChanged(trackGroups); - configurePlaybackHeadUpdateInterval(); - } - - @Override - public void onLoadingChanged(boolean isLoading) { - - } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - PlayerState state = this.getState(); - if (state == PlayerState.PLAYING_ADS) { - // Ignore all normal events while playing ads - return; + break; + case Player.STATE_ENDED: + ended(); + break; + case Player.STATE_READY: + // By the time we get here, it depends on playWhenReady to know if we're playing + if (playWhenReady) { + playing(); + } else if (state != PlayerState.PAUSED) { + pause(); } - switch (playbackState) { - case Player.STATE_BUFFERING: - // We have entered buffering - buffering(); - // If we are expected to playWhenReady, signal the play event - if (playWhenReady) { - play(); - } else if (state != PlayerState.PAUSED) { - pause(); - } - break; - case Player.STATE_ENDED: - ended(); - break; - case Player.STATE_READY: - // By the time we get here, it depends on playWhenReady to know if we're playing - if (playWhenReady) { - playing(); - } else if (state != PlayerState.PAUSED) { - pause(); - } - break; - case Player.STATE_IDLE: - default: - // don't care - break; - } - } - - @Override - public void onRepeatModeChanged(int repeatMode) { - - } - - @Override - public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { - - } - - @Override - public void onPlayerError(ExoPlaybackException e) { - if (e.type == ExoPlaybackException.TYPE_RENDERER) { - Exception cause = e.getRendererException(); - if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { - MediaCodecRenderer.DecoderInitializationException die = (MediaCodecRenderer.DecoderInitializationException) cause; - if (die.decoderName == null) { - if (die.getCause() instanceof MediaCodecUtil.DecoderQueryException) { - internalError(new MuxErrorException(e.type, "Unable to query device decoders")); - } else if (die.secureDecoderRequired) { - internalError(new MuxErrorException(e.type, "No secure decoder for " + die.mimeType)); - } else { - internalError(new MuxErrorException(e.type, "No decoder for " + die.mimeType)); - } - } else { - internalError(new MuxErrorException(e.type, "Unable to instantiate decoder for " + die.mimeType)); - } - } - else { - internalError(new MuxErrorException(e.type, cause.getClass().getCanonicalName() + " - " + cause.getMessage())); - } - } else if (e.type == ExoPlaybackException.TYPE_SOURCE) { - Exception error = e.getSourceException(); - internalError(new MuxErrorException(e.type, error.getClass().getCanonicalName() + " - " + error.getMessage())); - } else if (e.type == ExoPlaybackException.TYPE_UNEXPECTED) { - Exception error = e.getUnexpectedException(); - internalError(new MuxErrorException(e.type, error.getClass().getCanonicalName() + " - " + error.getMessage())); + break; + case Player.STATE_IDLE: + default: + // don't care + break; + } + } + + @Override + public void onRepeatModeChanged(int repeatMode) { + + } + + @Override + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { + + } + + @Override + public void onPlayerError(ExoPlaybackException e) { + if (e.type == ExoPlaybackException.TYPE_RENDERER) { + Exception cause = e.getRendererException(); + if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { + MediaCodecRenderer.DecoderInitializationException die = (MediaCodecRenderer.DecoderInitializationException) cause; + if (die.decoderName == null) { + if (die.getCause() instanceof MediaCodecUtil.DecoderQueryException) { + internalError(new MuxErrorException(e.type, "Unable to query device decoders")); + } else if (die.secureDecoderRequired) { + internalError(new MuxErrorException(e.type, "No secure decoder for " + die.mimeType)); + } else { + internalError(new MuxErrorException(e.type, "No decoder for " + die.mimeType)); + } } else { - internalError(e); + internalError( + new MuxErrorException(e.type, "Unable to instantiate decoder for " + die.mimeType)); } - } - - @Override - public void onPositionDiscontinuity(int reason) { - if (reason == Player.DISCONTINUITY_REASON_SEEK) { - if ( state == PlayerState.PAUSED || !playItemHaveVideoTrack ) { - seeked(false); - } - } - } - - @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - - } - - @Override - public void onSeekProcessed() { - } + } else { + internalError(new MuxErrorException(e.type, + cause.getClass().getCanonicalName() + " - " + cause.getMessage())); + } + } else if (e.type == ExoPlaybackException.TYPE_SOURCE) { + Exception error = e.getSourceException(); + internalError(new MuxErrorException(e.type, + error.getClass().getCanonicalName() + " - " + error.getMessage())); + } else if (e.type == ExoPlaybackException.TYPE_UNEXPECTED) { + Exception error = e.getUnexpectedException(); + internalError(new MuxErrorException(e.type, + error.getClass().getCanonicalName() + " - " + error.getMessage())); + } else { + internalError(e); + } + } + + @Override + public void onPositionDiscontinuity(int reason) { + if (reason == Player.DISCONTINUITY_REASON_SEEK) { + if (state == PlayerState.PAUSED || !playItemHaveVideoTrack) { + seeked(false); + } + } + } + + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + + } + + @Override + public void onSeekProcessed() { + } } diff --git a/README.md b/README.md index 051a9c25..2019c2df 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ See full integration instructions here: https://docs.mux.com/docs/exoplayer-inte Open this project in Android Studio, and let Gradle run to configure the application. Due to the breaking changes that occur within `ExoPlayer`'s minor version changes, this project is set up with a target for each minor version release of `ExoPlayer`, from r2.0.x up to r2.8.x. As additional versions of `ExoPlayer` are released, additional targets in this project will be added. Choose the target version that you desire, and run the demo application in the emulator to test out the functionality. +## Contributing +The code in this repo conforms to the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). Run the reformatter on files before committing. + +The code was formatted in Android Studio/IntelliJ using the [Google Java Style for IntelliJ](https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml). The style can be installed via the Java-style section of the IDE preferences (`Editor -> Code Style - >Java`). + ## Known Limitations - currently no ad events or metrics are supported - if an instance of `ExoPlayer` is passed, upscaling/downscaling and certain diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2d580e2b..543c4081 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,12 @@ # Release notes +## v2.4.4 + - Removed all content from res directory under MuxExoPlayer. (#92) + - Added test for playback end events and view end event. (#94) + - Reformat code with Google Java style (#88) + - Upgrade MuxCore to 6.2.0, which includes: + - Added viewEnd event on player release. + ## v2.4.3 - Propagate `customerViewData` through the constructors for 2.9.6, 2.11.1, and 2.12.1 as well. diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/AdsPlaybackTests.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/AdsPlaybackTests.java index 25740009..5b70d8c8 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/AdsPlaybackTests.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/AdsPlaybackTests.java @@ -1,6 +1,7 @@ package com.mux.stats.sdk.muxstats.automatedtests; +import static org.junit.Assert.fail; import com.mux.stats.sdk.core.events.playback.AdBreakStartEvent; import com.mux.stats.sdk.core.events.playback.AdEndedEvent; @@ -12,42 +13,38 @@ import com.mux.stats.sdk.core.events.playback.PlayingEvent; import com.mux.stats.sdk.muxstats.automatedtests.mockup.http.SimpleHTTPServer; import com.mux.stats.sdk.muxstats.automatedtests.ui.SimplePlayerTestActivity; - +import java.io.IOException; import org.junit.Before; import org.junit.Test; -import java.io.IOException; - -import static org.junit.Assert.fail; - public class AdsPlaybackTests extends TestBase { - static final int PREROLL_AD_PERIOD = 10000; - static final int BUMPER_AD_PERIOD = 5000; - static final int CAN_SKIP_AD_AFTER = 5000; + static final int PREROLL_AD_PERIOD = 10000; + static final int BUMPER_AD_PERIOD = 5000; + static final int CAN_SKIP_AD_AFTER = 5000; - @Before - public void init(){ - try { - httpServer = new SimpleHTTPServer(runHttpServerOnPort, bandwidthLimitInBitsPerSecond); + @Before + public void init() { + try { + httpServer = new SimpleHTTPServer(runHttpServerOnPort, bandwidthLimitInBitsPerSecond); // httpServer.setSeekLatency(SEEK_PERIOD_IN_MS); - } catch (IOException e) { - e.printStackTrace(); - // Failed to start server - fail("Failed to start HTTP server, why !!!"); - } - try { - testActivity = (SimplePlayerTestActivity) getActivityInstance(); - } catch (ClassCastException e) { - fail("Got wrong activity instance in test init !!!"); - } - if (testActivity == null) { - fail("Test activity not found !!!"); - } + } catch (IOException e) { + e.printStackTrace(); + // Failed to start server + fail("Failed to start HTTP server, why !!!"); } + try { + testActivity = (SimplePlayerTestActivity) getActivityInstance(); + } catch (ClassCastException e) { + fail("Got wrong activity instance in test init !!!"); + } + if (testActivity == null) { + fail("Test activity not found !!!"); + } + } - // Not working,, see how to reproduce + // Not working,, see how to reproduce // @Test // public void testPlayerReleasedWhileAdsPlaying() { // testActivity.runOnUiThread(() -> { @@ -81,108 +78,108 @@ public void init(){ // } // } -// @Test - public void testNoAdsPreRoll() { - - } - - @Test - public void testPreRollAndBumperAds() { - try { - testActivity.runOnUiThread(() -> { - testActivity.setVideoTitle( BuildConfig.FLAVOR + "-" + currentTestName.getMethodName() ); - testActivity.initMuxSats(); - testActivity.setUrlToPlay(urlToPlay); - testActivity.setAdTag("http://localhost:5000/preroll_and_bumper_vmap.xml"); - testActivity.startPlayback(); - pView = testActivity.getPlayerView(); - testMediaSource = testActivity.getTestMediaSource(); - networkRequest = testActivity.getMockNetwork(); - }); - if(!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { - fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); - } - // First ad is 10 second - Thread.sleep(PREROLL_AD_PERIOD / 2); - // Pause the ad for 5 seconds - pausePlayer(); - Thread.sleep(PAUSE_PERIOD_IN_MS); - // resume the ad playback - resumePlayer(); - Thread.sleep(PREROLL_AD_PERIOD + BUMPER_AD_PERIOD * 2); - - // Check ad start event - int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); - int pauseIndex = networkRequest.getIndexForFirstEvent(PauseEvent.TYPE); - int adBreakstartIndex = networkRequest.getIndexForFirstEvent(AdBreakStartEvent.TYPE); - int adPlayIndex = networkRequest.getIndexForFirstEvent(AdPlayEvent.TYPE); - int adPlayingIndex = networkRequest.getIndexForFirstEvent(AdPlayingEvent.TYPE); - - if (playIndex == -1 || pauseIndex == -1 - || adBreakstartIndex == -1 || adPlayIndex == -1 || adPlayingIndex == -1) { - fail("Missing basic start events ! playIndex: " + playIndex + - ", pauseIndex: " + pauseIndex + ", adBreakStartIndex: " + - adBreakstartIndex + ", adPlayIndex: " + adPlayIndex + ", adPlayingIndex: " - + adPlayingIndex); - } - if (!(playIndex < pauseIndex && pauseIndex < adBreakstartIndex - && adBreakstartIndex < adPlayIndex && adPlayIndex < adPlayingIndex) ) { - fail("Basic start events not ordered correctly ! playIndex: " + playIndex + - ", pauseIndex: " + pauseIndex + ", adBreakStartIndex: " + - adBreakstartIndex + ", adPlayIndex: " + adPlayIndex + ", adPlayingIndex: " - + adPlayingIndex); - } - - // Check first ad play period - int adPauseIndex = networkRequest.getIndexForFirstEvent(AdPauseEvent.TYPE); - long firstAdPlayPeriod = networkRequest.getCreationTimeForEvent(adPauseIndex) - - networkRequest.getCreationTimeForEvent(adPlayingIndex); - long expectedFirstAdPlayPeriod = PREROLL_AD_PERIOD / 2; - if (Math.abs(firstAdPlayPeriod - expectedFirstAdPlayPeriod) > 500) { - fail("First ad play period do not match expected play period, reported: " + - firstAdPlayPeriod + ", expected ad play period: " + expectedFirstAdPlayPeriod); - } - - // Check ad Pause - adPlayIndex = networkRequest.getIndexForNextEvent(adPauseIndex, AdPlayEvent.TYPE); - long firstAdPausePeriod = networkRequest.getCreationTimeForEvent(adPlayIndex) - - networkRequest.getCreationTimeForEvent(adPauseIndex); - if (Math.abs(firstAdPausePeriod - PAUSE_PERIOD_IN_MS) > 500) { - fail("First ad pause period do not match expected pause period, reported pause period: " + - firstAdPausePeriod + ", expected ad pause period: " + PAUSE_PERIOD_IN_MS); - } - - // Check rest of the first ad playback - int adEndedIndex = networkRequest.getIndexForNextEvent(adPlayIndex, AdEndedEvent.TYPE); - firstAdPlayPeriod = networkRequest.getCreationTimeForEvent(adEndedIndex) - - networkRequest.getCreationTimeForEvent(adPlayIndex); - if (Math.abs(firstAdPlayPeriod - expectedFirstAdPlayPeriod) > 500) { - fail("First ad play period do not match expected play period, reported: " + - firstAdPlayPeriod + ", expected ad play period: " + expectedFirstAdPlayPeriod); - } - - // Check bumper ad - adPlayingIndex = networkRequest.getIndexForNextEvent(adEndedIndex, AdPlayingEvent.TYPE); - adEndedIndex = networkRequest.getIndexForNextEvent(adPlayingIndex, AdEndedEvent.TYPE); - long bumperAdPlayPeriod = networkRequest.getCreationTimeForEvent(adEndedIndex) - - networkRequest.getCreationTimeForEvent(adPlayingIndex); - if (Math.abs(bumperAdPlayPeriod - BUMPER_AD_PERIOD) > 500) { - fail("Bumper ad period do not match expected bumper period, reported: " + - bumperAdPlayPeriod + ", expected ad play period: " + BUMPER_AD_PERIOD); - } - - // Check content play resume events - playIndex = networkRequest.getIndexForNextEvent(adEndedIndex, PlayEvent.TYPE); - int playingIndex = networkRequest.getIndexForNextEvent(adEndedIndex, PlayingEvent.TYPE); - if (playIndex == -1 || playingIndex == -1) { - fail("Missing play and playing events after adBreakEnd"); - } - if (playIndex >= playingIndex) { - fail("Play events after ad break are not ordered correctly"); - } - } catch (Exception e) { - fail(getExceptionFullTraceAndMessage( e )); - } + // @Test + public void testNoAdsPreRoll() { + + } + + @Test + public void testPreRollAndBumperAds() { + try { + testActivity.runOnUiThread(() -> { + testActivity.setVideoTitle(BuildConfig.FLAVOR + "-" + currentTestName.getMethodName()); + testActivity.initMuxSats(); + testActivity.setUrlToPlay(urlToPlay); + testActivity.setAdTag("http://localhost:5000/preroll_and_bumper_vmap.xml"); + testActivity.startPlayback(); + pView = testActivity.getPlayerView(); + testMediaSource = testActivity.getTestMediaSource(); + networkRequest = testActivity.getMockNetwork(); + }); + if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { + fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + // First ad is 10 second + Thread.sleep(PREROLL_AD_PERIOD / 2); + // Pause the ad for 5 seconds + pausePlayer(); + Thread.sleep(PAUSE_PERIOD_IN_MS); + // resume the ad playback + resumePlayer(); + Thread.sleep(PREROLL_AD_PERIOD + BUMPER_AD_PERIOD * 2); + + // Check ad start event + int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); + int pauseIndex = networkRequest.getIndexForFirstEvent(PauseEvent.TYPE); + int adBreakstartIndex = networkRequest.getIndexForFirstEvent(AdBreakStartEvent.TYPE); + int adPlayIndex = networkRequest.getIndexForFirstEvent(AdPlayEvent.TYPE); + int adPlayingIndex = networkRequest.getIndexForFirstEvent(AdPlayingEvent.TYPE); + + if (playIndex == -1 || pauseIndex == -1 + || adBreakstartIndex == -1 || adPlayIndex == -1 || adPlayingIndex == -1) { + fail("Missing basic start events ! playIndex: " + playIndex + + ", pauseIndex: " + pauseIndex + ", adBreakStartIndex: " + + adBreakstartIndex + ", adPlayIndex: " + adPlayIndex + ", adPlayingIndex: " + + adPlayingIndex); + } + if (!(playIndex < pauseIndex && pauseIndex < adBreakstartIndex + && adBreakstartIndex < adPlayIndex && adPlayIndex < adPlayingIndex)) { + fail("Basic start events not ordered correctly ! playIndex: " + playIndex + + ", pauseIndex: " + pauseIndex + ", adBreakStartIndex: " + + adBreakstartIndex + ", adPlayIndex: " + adPlayIndex + ", adPlayingIndex: " + + adPlayingIndex); + } + + // Check first ad play period + int adPauseIndex = networkRequest.getIndexForFirstEvent(AdPauseEvent.TYPE); + long firstAdPlayPeriod = networkRequest.getCreationTimeForEvent(adPauseIndex) - + networkRequest.getCreationTimeForEvent(adPlayingIndex); + long expectedFirstAdPlayPeriod = PREROLL_AD_PERIOD / 2; + if (Math.abs(firstAdPlayPeriod - expectedFirstAdPlayPeriod) > 500) { + fail("First ad play period do not match expected play period, reported: " + + firstAdPlayPeriod + ", expected ad play period: " + expectedFirstAdPlayPeriod); + } + + // Check ad Pause + adPlayIndex = networkRequest.getIndexForNextEvent(adPauseIndex, AdPlayEvent.TYPE); + long firstAdPausePeriod = networkRequest.getCreationTimeForEvent(adPlayIndex) - + networkRequest.getCreationTimeForEvent(adPauseIndex); + if (Math.abs(firstAdPausePeriod - PAUSE_PERIOD_IN_MS) > 500) { + fail("First ad pause period do not match expected pause period, reported pause period: " + + firstAdPausePeriod + ", expected ad pause period: " + PAUSE_PERIOD_IN_MS); + } + + // Check rest of the first ad playback + int adEndedIndex = networkRequest.getIndexForNextEvent(adPlayIndex, AdEndedEvent.TYPE); + firstAdPlayPeriod = networkRequest.getCreationTimeForEvent(adEndedIndex) - + networkRequest.getCreationTimeForEvent(adPlayIndex); + if (Math.abs(firstAdPlayPeriod - expectedFirstAdPlayPeriod) > 500) { + fail("First ad play period do not match expected play period, reported: " + + firstAdPlayPeriod + ", expected ad play period: " + expectedFirstAdPlayPeriod); + } + + // Check bumper ad + adPlayingIndex = networkRequest.getIndexForNextEvent(adEndedIndex, AdPlayingEvent.TYPE); + adEndedIndex = networkRequest.getIndexForNextEvent(adPlayingIndex, AdEndedEvent.TYPE); + long bumperAdPlayPeriod = networkRequest.getCreationTimeForEvent(adEndedIndex) - + networkRequest.getCreationTimeForEvent(adPlayingIndex); + if (Math.abs(bumperAdPlayPeriod - BUMPER_AD_PERIOD) > 500) { + fail("Bumper ad period do not match expected bumper period, reported: " + + bumperAdPlayPeriod + ", expected ad play period: " + BUMPER_AD_PERIOD); + } + + // Check content play resume events + playIndex = networkRequest.getIndexForNextEvent(adEndedIndex, PlayEvent.TYPE); + int playingIndex = networkRequest.getIndexForNextEvent(adEndedIndex, PlayingEvent.TYPE); + if (playIndex == -1 || playingIndex == -1) { + fail("Missing play and playing events after adBreakEnd"); + } + if (playIndex >= playingIndex) { + fail("Play events after ad break are not ordered correctly"); + } + } catch (Exception e) { + fail(getExceptionFullTraceAndMessage(e)); } + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/AudioPlaybackTests.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/AudioPlaybackTests.java index 9d34bba8..1f2717a8 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/AudioPlaybackTests.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/AudioPlaybackTests.java @@ -3,24 +3,22 @@ import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.fail; - public class AudioPlaybackTests extends SeekingTestBase { - @Before - public void init(){ - urlToPlay = "http://localhost:5000/audio.aac"; - super.init(); - } + @Before + public void init() { + urlToPlay = "http://localhost:5000/audio.aac"; + super.init(); + } - @Test - public void testSeekingWhilePausedAudioOnly() { - testSeekingWhilePaused(); - } + @Test + public void testSeekingWhilePausedAudioOnly() { + testSeekingWhilePaused(); + } - @Test - public void testSeekingWhilePlayingAudioOnly() { - testSeekingWhilePlaying(); - } + @Test + public void testSeekingWhilePlayingAudioOnly() { + testSeekingWhilePlaying(); + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/MissusageTests.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/MissusageTests.java index 4f241fde..5d6b8c0f 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/MissusageTests.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/MissusageTests.java @@ -1,46 +1,40 @@ package com.mux.stats.sdk.muxstats.automatedtests; -import android.os.Bundle; -import android.os.Handler; +import static org.junit.Assert.fail; import com.mux.stats.sdk.core.events.playback.PlayEvent; import com.mux.stats.sdk.core.events.playback.PlayingEvent; import com.mux.stats.sdk.core.events.playback.ViewStartEvent; import com.mux.stats.sdk.muxstats.automatedtests.mockup.http.SimpleHTTPServer; -import com.mux.stats.sdk.muxstats.automatedtests.ui.SimplePlayerBaseActivity; import com.mux.stats.sdk.muxstats.automatedtests.ui.SimplePlayerTestActivity; - +import java.io.IOException; import org.junit.Before; import org.junit.Test; -import java.io.IOException; - -import static org.junit.Assert.fail; - public class MissusageTests extends TestBase { - static final int INIT_MUX_STATS_AFTER = 5000; + static final int INIT_MUX_STATS_AFTER = 5000; - @Before - public void init(){ - try { - httpServer = new SimpleHTTPServer(runHttpServerOnPort, bandwidthLimitInBitsPerSecond); - } catch (IOException e) { - e.printStackTrace(); - // Failed to start server - fail("Failed to start HTTP server, why !!!"); - } - try { - testActivity = (SimplePlayerTestActivity) getActivityInstance(); - } catch (ClassCastException e) { - fail("Got wrong activity instance in test init !!!"); - } - if (testActivity == null) { - fail("Test activity not found !!!"); - } + @Before + public void init() { + try { + httpServer = new SimpleHTTPServer(runHttpServerOnPort, bandwidthLimitInBitsPerSecond); + } catch (IOException e) { + e.printStackTrace(); + // Failed to start server + fail("Failed to start HTTP server, why !!!"); + } + try { + testActivity = (SimplePlayerTestActivity) getActivityInstance(); + } catch (ClassCastException e) { + fail("Got wrong activity instance in test init !!!"); + } + if (testActivity == null) { + fail("Test activity not found !!!"); } + } - // Not working, find out how to reproduce Thread safe crash + // Not working, find out how to reproduce Thread safe crash // @Test // public void testPlayerReleasedWhileStatsRunning() { // try { @@ -71,51 +65,51 @@ public void init(){ // } // } - @Test - public void testLateStatsInit() { - try { - // Init test activity but not the Mux stats - testActivity.runOnUiThread(() -> { - testActivity.setVideoTitle( BuildConfig.FLAVOR + "-" + currentTestName.getMethodName() ); - testActivity.setUrlToPlay(urlToPlay); - testActivity.startPlayback(); - pView = testActivity.getPlayerView(); - testMediaSource = testActivity.getTestMediaSource(); - }); - if(!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { - fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); - } - Thread.sleep(INIT_MUX_STATS_AFTER); - // Init Mux stats after the playback have started - testActivity.runOnUiThread(() -> { - testActivity.initMuxSats(); - }); - Thread.sleep(INIT_MUX_STATS_AFTER * 2); - // This is initialized with the MuxStats, it need to be called after - // testActivity.initMuxSats(); - networkRequest = testActivity.getMockNetwork(); - // Check if play, playing and etc events are sent - int viewstartIndex = networkRequest.getIndexForFirstEvent(ViewStartEvent.TYPE); - int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); - int playingIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); - if (viewstartIndex == -1 || playIndex == -1 || playingIndex== -1) { - fail("Missing playback starting events, viewstartIndex: " + viewstartIndex + - ", playIndex: " + playIndex + ", playingIndex: " + playingIndex + - " RECEIVED: " + networkRequest.getReceivedEventNames()); - } - if (!(viewstartIndex < playIndex && playIndex < playingIndex)) { - fail("Playback starting events not received in correct order, viewstartIndex: " - + viewstartIndex + ", playIndex: " + playIndex + ", playingIndex: " - + playingIndex + " RECEIVED: " + networkRequest.getReceivedEventNames()); - } - long playReceivedTime = networkRequest.getCreationTimeForEvent(playIndex) - - networkRequest.getCreationTimeForEvent(viewstartIndex); - if (playReceivedTime > 500) { - fail("Play event received after: " + playReceivedTime + ", expected less the 500 ms"); - } - } catch (Exception e) { - fail(getExceptionFullTraceAndMessage( e )); - } - + @Test + public void testLateStatsInit() { + try { + // Init test activity but not the Mux stats + testActivity.runOnUiThread(() -> { + testActivity.setVideoTitle(BuildConfig.FLAVOR + "-" + currentTestName.getMethodName()); + testActivity.setUrlToPlay(urlToPlay); + testActivity.startPlayback(); + pView = testActivity.getPlayerView(); + testMediaSource = testActivity.getTestMediaSource(); + }); + if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { + fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + Thread.sleep(INIT_MUX_STATS_AFTER); + // Init Mux stats after the playback have started + testActivity.runOnUiThread(() -> { + testActivity.initMuxSats(); + }); + Thread.sleep(INIT_MUX_STATS_AFTER * 2); + // This is initialized with the MuxStats, it need to be called after + // testActivity.initMuxSats(); + networkRequest = testActivity.getMockNetwork(); + // Check if play, playing and etc events are sent + int viewstartIndex = networkRequest.getIndexForFirstEvent(ViewStartEvent.TYPE); + int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); + int playingIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); + if (viewstartIndex == -1 || playIndex == -1 || playingIndex == -1) { + fail("Missing playback starting events, viewstartIndex: " + viewstartIndex + + ", playIndex: " + playIndex + ", playingIndex: " + playingIndex + + " RECEIVED: " + networkRequest.getReceivedEventNames()); + } + if (!(viewstartIndex < playIndex && playIndex < playingIndex)) { + fail("Playback starting events not received in correct order, viewstartIndex: " + + viewstartIndex + ", playIndex: " + playIndex + ", playingIndex: " + + playingIndex + " RECEIVED: " + networkRequest.getReceivedEventNames()); + } + long playReceivedTime = networkRequest.getCreationTimeForEvent(playIndex) - + networkRequest.getCreationTimeForEvent(viewstartIndex); + if (playReceivedTime > 500) { + fail("Play event received after: " + playReceivedTime + ", expected less the 500 ms"); + } + } catch (Exception e) { + fail(getExceptionFullTraceAndMessage(e)); } + + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/PlaybackTests.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/PlaybackTests.java index 0853cc08..a9595087 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/PlaybackTests.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/PlaybackTests.java @@ -1,28 +1,21 @@ package com.mux.stats.sdk.muxstats.automatedtests; -import android.os.Bundle; +import static org.junit.Assert.fail; + import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.google.android.exoplayer2.ControlDispatcher; -import com.google.android.exoplayer2.DefaultControlDispatcher; -import com.google.android.exoplayer2.Player; +import com.mux.stats.sdk.core.events.playback.EndedEvent; import com.mux.stats.sdk.core.events.playback.PauseEvent; import com.mux.stats.sdk.core.events.playback.PlayEvent; import com.mux.stats.sdk.core.events.playback.PlayingEvent; import com.mux.stats.sdk.core.events.playback.RebufferEndEvent; import com.mux.stats.sdk.core.events.playback.RebufferStartEvent; +import com.mux.stats.sdk.core.events.playback.ViewEndEvent; import com.mux.stats.sdk.core.events.playback.ViewStartEvent; import com.mux.stats.sdk.muxstats.R; - -import org.json.JSONException; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import static org.junit.Assert.fail; - /** * Instrumented test, which will execute on an Android device. * @@ -31,190 +24,228 @@ @RunWith(AndroidJUnit4.class) public class PlaybackTests extends SeekingTestBase { - public static final String TAG = "playbackTest"; - - /* - * Test Seeking, event order - */ - @Test - public void testSeekingWhilePausedVideoAndAudio() { - testSeekingWhilePaused(); + public static final String TAG = "playbackTest"; + + @Test + public void testEndEvents() { + try { + if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { + fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + // Seek backward, stage 4 + testActivity.runOnUiThread(new Runnable() { + public void run() { + long contentDuration = pView.getPlayer().getContentDuration(); + pView.getPlayer().seekTo(contentDuration - 2000); + } + }); + if (!testActivity.waitForPlaybackToFinish(waitForPlaybackToStartInMS)) { + fail("Playback did not finish in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + testActivity.finishAffinity(); + Thread.sleep(PAUSE_PERIOD_IN_MS); + int pauseIndex = networkRequest.getIndexForFirstEvent(PauseEvent.TYPE); + int endedIndex = networkRequest.getIndexForFirstEvent(EndedEvent.TYPE); + int viewEndEventIndex = networkRequest.getIndexForFirstEvent(ViewEndEvent.TYPE); + if (viewEndEventIndex == -1 || endedIndex == -1 || pauseIndex == -1) { + fail("Missing end events: viewEndEventIndex = " + viewEndEventIndex + + ", viewEndEventIndex: " + viewEndEventIndex + + ", pauseEventIndex: " + pauseIndex); + } + if (!(pauseIndex < endedIndex && endedIndex < viewEndEventIndex)) { + fail("End events not ordered correctly: viewEndEventIndex = " + viewEndEventIndex + + ", viewEndEventIndex: " + viewEndEventIndex + + ", pauseEventIndex: " + pauseIndex); + } + } catch (Exception e) { + fail(getExceptionFullTraceAndMessage(e)); } + } + + /* + * Test Seeking, event order + */ + @Test + public void testSeekingWhilePausedVideoAndAudio() { + testSeekingWhilePaused(); + } + + @Test + public void testSeekingWhilePlayingVideoAndAudio() { + testSeekingWhilePlaying(); + } + + /* + * According to the self validation guid: https://docs.google.com/document/d/1FU_09N3Cg9xfh784edBJpgg3YVhzBA6-bd5XHLK7IK4/edit# + * We are implementing vod playback scenario. + */ + @Test + public void testVodPlayback() { + try { + if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { + fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + + // Init player controlls + controlView = pView.findViewById(R.id.exo_controller); + if (controlView != null) { + pauseButton = controlView.findViewById(R.id.exo_pause); + playButton = controlView.findViewById(R.id.exo_play); + } + initPlayerControls(); + + // play x seconds, stage 1 + Thread.sleep(PLAY_PERIOD_IN_MS); + pausePlayer(); + // Pause x seconds, stage 2 + Thread.sleep(PAUSE_PERIOD_IN_MS); + // Resume video, stage 3 + resumePlayer(); + // Play another x seconds + Thread.sleep(PLAY_PERIOD_IN_MS); + + // Seek backward, stage 4 + testActivity.runOnUiThread(new Runnable() { + public void run() { + long currentPlaybackPosition = pView.getPlayer().getCurrentPosition(); + pView.getPlayer().seekTo(currentPlaybackPosition / 2); + } + }); + + // Play another x seconds, stage 5 + Thread.sleep(PLAY_PERIOD_IN_MS); + + // seek forward in the video, stage 6 + testActivity.runOnUiThread(new Runnable() { + public void run() { + long currentPlaybackPosition = pView.getPlayer() + .getCurrentPosition(); + long videoDuration = pView.getPlayer().getDuration(); + long seekToInFuture = + currentPlaybackPosition + ((videoDuration - currentPlaybackPosition) / 2); + pView.getPlayer().seekTo(seekToInFuture); + } + }); - @Test - public void testSeekingWhilePlayingVideoAndAudio() { - testSeekingWhilePlaying(); - } + // Play another x seconds, stage 7 + Thread.sleep(PLAY_PERIOD_IN_MS * 2); - /* - * According to the self validation guid: https://docs.google.com/document/d/1FU_09N3Cg9xfh784edBJpgg3YVhzBA6-bd5XHLK7IK4/edit# - * We are implementing vod playback scenario. - */ - @Test - public void testVodPlayback() { - try { - if(!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { - fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); - } - - // Init player controlls - controlView = pView.findViewById(R.id.exo_controller); - if (controlView != null) { - pauseButton = controlView.findViewById(R.id.exo_pause); - playButton = controlView.findViewById(R.id.exo_play); - } - initPlayerControls(); - - // play x seconds, stage 1 - Thread.sleep(PLAY_PERIOD_IN_MS); - pausePlayer(); - // Pause x seconds, stage 2 - Thread.sleep(PAUSE_PERIOD_IN_MS); - // Resume video, stage 3 - resumePlayer(); - // Play another x seconds - Thread.sleep(PLAY_PERIOD_IN_MS); - - // Seek backward, stage 4 - testActivity.runOnUiThread(new Runnable(){ - public void run() { - long currentPlaybackPosition = pView.getPlayer().getCurrentPosition(); - pView.getPlayer().seekTo(currentPlaybackPosition/2); - } - }); - - // Play another x seconds, stage 5 - Thread.sleep(PLAY_PERIOD_IN_MS); - - // seek forward in the video, stage 6 - testActivity.runOnUiThread(new Runnable(){ - public void run() { - long currentPlaybackPosition = pView.getPlayer() - .getCurrentPosition(); - long videoDuration = pView.getPlayer().getDuration(); - long seekToInFuture = currentPlaybackPosition + ((videoDuration - currentPlaybackPosition) / 2); - pView.getPlayer().seekTo(seekToInFuture); - } - }); - - // Play another x seconds, stage 7 - Thread.sleep(PLAY_PERIOD_IN_MS * 2); - - CheckupResult result; - - // Check first playback period, stage 1 - result = checkPlaybackPeriodAtIndex(0, PLAY_PERIOD_IN_MS); - - // Check pause period, stage 2 - result = checkPausePeriodAtIndex(result.eventIndex, PAUSE_PERIOD_IN_MS); - - // Check playback period, stage 3 - result = checkPlaybackPeriodAtIndex(result.eventIndex -1, PLAY_PERIOD_IN_MS); - - // Check SeekEvents, stage 4 - result = checkSeekAtIndex(result.eventIndex); - - // check playback period stage 5 - result = checkPlaybackPeriodAtIndex(result.eventIndex, - PLAY_PERIOD_IN_MS - result.seekPeriod); - - // check seeking, stage 6 - result = checkSeekAtIndex(result.eventIndex); - - // Exit the player with back button + CheckupResult result; + + // Check first playback period, stage 1 + result = checkPlaybackPeriodAtIndex(0, PLAY_PERIOD_IN_MS); + + // Check pause period, stage 2 + result = checkPausePeriodAtIndex(result.eventIndex, PAUSE_PERIOD_IN_MS); + + // Check playback period, stage 3 + result = checkPlaybackPeriodAtIndex(result.eventIndex - 1, PLAY_PERIOD_IN_MS); + + // Check SeekEvents, stage 4 + result = checkSeekAtIndex(result.eventIndex); + + // check playback period stage 5 + result = checkPlaybackPeriodAtIndex(result.eventIndex, + PLAY_PERIOD_IN_MS - result.seekPeriod); + + // check seeking, stage 6 + result = checkSeekAtIndex(result.eventIndex); + + // Exit the player with back button // testScenario.close(); - Log.w(TAG, "See what event should be dispatched on view closed !!!"); - // TODO check player end event - } catch (Exception e) { - fail(getExceptionFullTraceAndMessage( e )); - } - Log.e(TAG, "All done !!!"); + Log.w(TAG, "See what event should be dispatched on view closed !!!"); + // TODO check player end event + } catch (Exception e) { + fail(getExceptionFullTraceAndMessage(e)); } - - @Test - public void testRebufferingAndStartupTime() { - try { - testActivity.waitForActivityToInitialize(); - long testStartedAt = System.currentTimeMillis(); - if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { - fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); - } - long expectedStartupTime = System.currentTimeMillis() - testStartedAt; - - // play x seconds - Thread.sleep(PLAY_PERIOD_IN_MS); - jamNetwork(); - testActivity.waitForPlaybackToStartBuffering(); - long rebufferStartedAT = System.currentTimeMillis(); - - // Wait for rebuffer to complete - if(!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { - fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); - } - - long measuredRebufferPeriod = System.currentTimeMillis() - rebufferStartedAT; - // play x seconds - Thread.sleep(PLAY_PERIOD_IN_MS * 2); + Log.e(TAG, "All done !!!"); + } + + @Test + public void testRebufferingAndStartupTime() { + try { + testActivity.waitForActivityToInitialize(); + long testStartedAt = System.currentTimeMillis(); + if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { + fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + long expectedStartupTime = System.currentTimeMillis() - testStartedAt; + + // play x seconds + Thread.sleep(PLAY_PERIOD_IN_MS); + jamNetwork(); + testActivity.waitForPlaybackToStartBuffering(); + long rebufferStartedAT = System.currentTimeMillis(); + + // Wait for rebuffer to complete + if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { + fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + + long measuredRebufferPeriod = System.currentTimeMillis() - rebufferStartedAT; + // play x seconds + Thread.sleep(PLAY_PERIOD_IN_MS * 2); // exitActivity(); // testScenario.close(); - // Startup time check - int viewstartIndex = networkRequest.getIndexForFirstEvent(ViewStartEvent.TYPE); - int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); - int playingIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); - // Check if viewstart and playing events are received - if (viewstartIndex == -1) { - fail("viewstart event not received !!!"); - } - if (playIndex == -1) { - fail("play event not received !!!"); - } - if (playingIndex == -1) { - fail("playing event not received !!!"); - } - - long reportedStartupTime = networkRequest.getCreationTimeForEvent(playingIndex) - - networkRequest.getCreationTimeForEvent(viewstartIndex); - // Check if startup time match with in 200 ms precission - if (Math.abs(reportedStartupTime - expectedStartupTime) > 500) { - fail("Reported startup time and expected startup time do not match within 500 ms," - + "reported time: " + reportedStartupTime + ", measured startup time: " + expectedStartupTime); - } - - // check rebuffering events - int rebufferStartEventIndex = networkRequest.getIndexForFirstEvent(RebufferStartEvent.TYPE); - int rebufferEndEventIndex = networkRequest.getIndexForFirstEvent(RebufferEndEvent.TYPE); - // Check if rebuffer events are received - if (rebufferStartEventIndex == -1) { - fail("rebufferstart event not received !!!"); - } - if(rebufferEndEventIndex == -1) { - fail("rebufferend event not received !!!"); - } - if (rebufferStartEventIndex > rebufferEndEventIndex) { - fail("rebufferend received before rebufferstart event !!!"); - } - int secondPlayIndex = networkRequest.getIndexForLastEvent(PlayEvent.TYPE); - int secondPlayingIndex = networkRequest.getIndexForLastEvent(PlayingEvent.TYPE); - if (secondPlayIndex != playIndex) { - fail("Play event received after rebufferend this is not good ! event: " - + networkRequest.getReceivedEventNames()); - } - if (secondPlayingIndex == playingIndex) { - fail("Playing event not received after ebufferEnd ! events: " - + networkRequest.getReceivedEventNames()); - } - // TODO see what is the best way to calculate rebuffer period - } catch (Exception e) { - fail(getExceptionFullTraceAndMessage( e )); - } + // Startup time check + int viewstartIndex = networkRequest.getIndexForFirstEvent(ViewStartEvent.TYPE); + int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); + int playingIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); + // Check if viewstart and playing events are received + if (viewstartIndex == -1) { + fail("viewstart event not received !!!"); + } + if (playIndex == -1) { + fail("play event not received !!!"); + } + if (playingIndex == -1) { + fail("playing event not received !!!"); + } + + long reportedStartupTime = networkRequest.getCreationTimeForEvent(playingIndex) - + networkRequest.getCreationTimeForEvent(viewstartIndex); + // Check if startup time match with in 200 ms precission + if (Math.abs(reportedStartupTime - expectedStartupTime) > 500) { + fail("Reported startup time and expected startup time do not match within 500 ms," + + "reported time: " + reportedStartupTime + ", measured startup time: " + + expectedStartupTime); + } + + // check rebuffering events + int rebufferStartEventIndex = networkRequest.getIndexForFirstEvent(RebufferStartEvent.TYPE); + int rebufferEndEventIndex = networkRequest.getIndexForFirstEvent(RebufferEndEvent.TYPE); + // Check if rebuffer events are received + if (rebufferStartEventIndex == -1) { + fail("rebufferstart event not received !!!"); + } + if (rebufferEndEventIndex == -1) { + fail("rebufferend event not received !!!"); + } + if (rebufferStartEventIndex > rebufferEndEventIndex) { + fail("rebufferend received before rebufferstart event !!!"); + } + int secondPlayIndex = networkRequest.getIndexForLastEvent(PlayEvent.TYPE); + int secondPlayingIndex = networkRequest.getIndexForLastEvent(PlayingEvent.TYPE); + if (secondPlayIndex != playIndex) { + fail("Play event received after rebufferend this is not good ! event: " + + networkRequest.getReceivedEventNames()); + } + if (secondPlayingIndex == playingIndex) { + fail("Playing event not received after ebufferEnd ! events: " + + networkRequest.getReceivedEventNames()); + } + // TODO see what is the best way to calculate rebuffer period + } catch (Exception e) { + fail(getExceptionFullTraceAndMessage(e)); } + } - void initPlayerControls() { - controlView = pView.findViewById(R.id.exo_controller); - if (controlView != null) { - pauseButton = controlView.findViewById(R.id.exo_pause); - playButton = controlView.findViewById(R.id.exo_play); - } + void initPlayerControls() { + controlView = pView.findViewById(R.id.exo_controller); + if (controlView != null) { + pauseButton = controlView.findViewById(R.id.exo_pause); + playButton = controlView.findViewById(R.id.exo_play); } + } } \ No newline at end of file diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/RenditionChangeTests.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/RenditionChangeTests.java index d8dc158e..497d54eb 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/RenditionChangeTests.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/RenditionChangeTests.java @@ -1,7 +1,9 @@ package com.mux.stats.sdk.muxstats.automatedtests; -import android.util.Log; +import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO; +import static org.junit.Assert.fail; +import android.util.Log; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -10,159 +12,155 @@ import com.mux.stats.sdk.core.events.playback.PlayingEvent; import com.mux.stats.sdk.core.events.playback.RenditionChangeEvent; import com.mux.stats.sdk.core.model.VideoData; -import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; - +import java.util.ArrayList; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; -import java.util.ArrayList; - -import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO; -import static org.junit.Assert.fail; - public class RenditionChangeTests extends TestBase { - static final String TAG = "RenditionChangeTests"; - - @Before - public void init(){ - urlToPlay = "http://localhost:5000/hls/google_glass/playlist.m3u8"; - // These video have larger bitrate, make sure we do not cause any - // rebuffering due to low bandwith - bandwidthLimitInBitsPerSecond = 12000000; - super.init(); - } - - @Test - public void testRenditionChange() { - try { - if(!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { - fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); - } - Thread.sleep(PLAY_PERIOD_IN_MS); - // Switch rendition - int startingFmtIndex = getSelectedRenditionIndex(); - ArrayList availableFormats = getAvailableVideoRendition(); - Format startingFmt = availableFormats.get(startingFmtIndex); - int nextFmtIndex; - if (startingFmtIndex == availableFormats.size() - 1) { - nextFmtIndex = startingFmtIndex - 1; - } else { - nextFmtIndex = startingFmtIndex + 1; - } - Format changedFmt = availableFormats.get(nextFmtIndex); - switchRenditionToIndex(nextFmtIndex); - Thread.sleep(PLAY_PERIOD_IN_MS); - int renditionChangeIndex = 0; - int playinIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); - JSONArray receivedRenditionChangeEvents = new JSONArray(); - while (true) { - renditionChangeIndex = networkRequest - .getIndexForNextEvent(renditionChangeIndex + 1, RenditionChangeEvent.TYPE); - long lastRenditionChangeAt = networkRequest - .getCreationTimeForEvent(renditionChangeIndex) - networkRequest - .getCreationTimeForEvent(playinIndex); - if (renditionChangeIndex == -1) { - fail("Failed to find RenditionChangeEvent dispatched after: " - + PLAY_PERIOD_IN_MS + " ms since playback started, with valid data" - + ", received events: " + receivedRenditionChangeEvents.toString()); - } - JSONObject jo = networkRequest.getEventForIndex(renditionChangeIndex); - receivedRenditionChangeEvents.put(jo); - if (Math.abs(lastRenditionChangeAt - PLAY_PERIOD_IN_MS) < 500) { - // We found rendition change index we ware looking for, there may be more after, - // because I dont know how to controll the player bitadaptive settings - if ( !jo.has(VideoData.VIDEO_SOURCE_WIDTH) || ! jo.has(VideoData.VIDEO_SOURCE_HEIGHT)) { - Log.w(TAG, "Missing video width and/or video height parameters on Rendition change event, " - + " json: " + jo.toString()); - continue; - } - break; - } - } - - JSONObject jo = networkRequest.getEventForIndex(renditionChangeIndex); - int videoWidth = jo.getInt(VideoData.VIDEO_SOURCE_WIDTH); - int videoHeight = jo.getInt(VideoData.VIDEO_SOURCE_HEIGHT); - if (videoWidth != changedFmt.width && videoHeight != changedFmt.height) { - fail("Last reported rendition change width and height (" + videoWidth + "x" + - videoHeight + ") do not match requested format resolution: (" + - changedFmt.width + "x" + changedFmt.height + ")"); - } - } catch (Exception e) { - fail(getExceptionFullTraceAndMessage( e )); + static final String TAG = "RenditionChangeTests"; + + @Before + public void init() { + urlToPlay = "http://localhost:5000/hls/google_glass/playlist.m3u8"; + // These video have larger bitrate, make sure we do not cause any + // rebuffering due to low bandwith + bandwidthLimitInBitsPerSecond = 12000000; + super.init(); + } + + @Test + public void testRenditionChange() { + try { + if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { + fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + Thread.sleep(PLAY_PERIOD_IN_MS); + // Switch rendition + int startingFmtIndex = getSelectedRenditionIndex(); + ArrayList availableFormats = getAvailableVideoRendition(); + Format startingFmt = availableFormats.get(startingFmtIndex); + int nextFmtIndex; + if (startingFmtIndex == availableFormats.size() - 1) { + nextFmtIndex = startingFmtIndex - 1; + } else { + nextFmtIndex = startingFmtIndex + 1; + } + Format changedFmt = availableFormats.get(nextFmtIndex); + switchRenditionToIndex(nextFmtIndex); + Thread.sleep(PLAY_PERIOD_IN_MS); + int renditionChangeIndex = 0; + int playinIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); + JSONArray receivedRenditionChangeEvents = new JSONArray(); + while (true) { + renditionChangeIndex = networkRequest + .getIndexForNextEvent(renditionChangeIndex + 1, RenditionChangeEvent.TYPE); + long lastRenditionChangeAt = networkRequest + .getCreationTimeForEvent(renditionChangeIndex) - networkRequest + .getCreationTimeForEvent(playinIndex); + if (renditionChangeIndex == -1) { + fail("Failed to find RenditionChangeEvent dispatched after: " + + PLAY_PERIOD_IN_MS + " ms since playback started, with valid data" + + ", received events: " + receivedRenditionChangeEvents.toString()); } - } - - TrackGroupArray getVideoTrackGroupArray() { - DefaultTrackSelector selector = testActivity.getTrackSelector(); - MappingTrackSelector.MappedTrackInfo mappedTrackInfo = selector.getCurrentMappedTrackInfo(); - for(int rendererTrackIndex = 0; rendererTrackIndex < mappedTrackInfo.getRendererCount(); - rendererTrackIndex++) { - int trackType = mappedTrackInfo.getRendererType(rendererTrackIndex); - if (trackType == TRACK_TYPE_VIDEO) { - return mappedTrackInfo - .getTrackGroups(rendererTrackIndex); - } - + JSONObject jo = networkRequest.getEventForIndex(renditionChangeIndex); + receivedRenditionChangeEvents.put(jo); + if (Math.abs(lastRenditionChangeAt - PLAY_PERIOD_IN_MS) < 500) { + // We found rendition change index we ware looking for, there may be more after, + // because I dont know how to controll the player bitadaptive settings + if (!jo.has(VideoData.VIDEO_SOURCE_WIDTH) || !jo.has(VideoData.VIDEO_SOURCE_HEIGHT)) { + Log.w(TAG, + "Missing video width and/or video height parameters on Rendition change event, " + + " json: " + jo.toString()); + continue; + } + break; } - return null; + } + + JSONObject jo = networkRequest.getEventForIndex(renditionChangeIndex); + int videoWidth = jo.getInt(VideoData.VIDEO_SOURCE_WIDTH); + int videoHeight = jo.getInt(VideoData.VIDEO_SOURCE_HEIGHT); + if (videoWidth != changedFmt.width && videoHeight != changedFmt.height) { + fail("Last reported rendition change width and height (" + videoWidth + "x" + + videoHeight + ") do not match requested format resolution: (" + + changedFmt.width + "x" + changedFmt.height + ")"); + } + } catch (Exception e) { + fail(getExceptionFullTraceAndMessage(e)); } + } + + TrackGroupArray getVideoTrackGroupArray() { + DefaultTrackSelector selector = testActivity.getTrackSelector(); + MappingTrackSelector.MappedTrackInfo mappedTrackInfo = selector.getCurrentMappedTrackInfo(); + for (int rendererTrackIndex = 0; rendererTrackIndex < mappedTrackInfo.getRendererCount(); + rendererTrackIndex++) { + int trackType = mappedTrackInfo.getRendererType(rendererTrackIndex); + if (trackType == TRACK_TYPE_VIDEO) { + return mappedTrackInfo + .getTrackGroups(rendererTrackIndex); + } - ArrayList getAvailableVideoRendition() { - ArrayList result = new ArrayList<>(); - TrackGroupArray videoTrackGroupArray = getVideoTrackGroupArray(); - for (int trackIndex = 0; trackIndex < videoTrackGroupArray.length; trackIndex++) { - TrackGroup trackGroup = videoTrackGroupArray.get(trackIndex); - for (int formatIndex = 0; formatIndex < trackGroup.length; formatIndex ++) { - result.add(trackGroup.getFormat(formatIndex)); - } - } - return result; } + return null; + } + + ArrayList getAvailableVideoRendition() { + ArrayList result = new ArrayList<>(); + TrackGroupArray videoTrackGroupArray = getVideoTrackGroupArray(); + for (int trackIndex = 0; trackIndex < videoTrackGroupArray.length; trackIndex++) { + TrackGroup trackGroup = videoTrackGroupArray.get(trackIndex); + for (int formatIndex = 0; formatIndex < trackGroup.length; formatIndex++) { + result.add(trackGroup.getFormat(formatIndex)); + } + } + return result; + } - int getSelectedRenditionIndex() throws JSONException { + int getSelectedRenditionIndex() throws JSONException { // MuxStatsExoPlayer lMuxStats = testActivity.getMuxStats(); // int videoWidth = lMuxStats.getSourceWidth(); // int videoHeight = lMuxStats.getSourceHeight(); - int renditionChangeIndex = networkRequest.getIndexForFirstEvent(RenditionChangeEvent.TYPE); - if ( renditionChangeIndex == -1 ) { - return -1; - } - JSONObject jo = networkRequest.getEventForIndex(renditionChangeIndex); - if ( jo == null || !jo.has(VideoData.VIDEO_SOURCE_WIDTH) || ! jo.has(VideoData.VIDEO_SOURCE_HEIGHT)) { - return -1; - } - int videoWidth = jo.getInt(VideoData.VIDEO_SOURCE_WIDTH); - int videoHeight = jo.getInt(VideoData.VIDEO_SOURCE_HEIGHT); - - ArrayList availableFormats = getAvailableVideoRendition(); - int index = 0; - for (Format fmt:availableFormats) { - if (Math.abs(fmt.width - videoWidth) < 10 && - Math.abs(fmt.height - videoHeight) < 10) { - return index; - } - index ++; - } - String availableFormatsStr = ""; - for (Format fmt : availableFormats) { - availableFormatsStr += "(" + fmt.width + "x" + fmt.height + ") "; - } - fail("Reported source resolution: " + videoWidth + "x" + videoHeight + - " do not match any of available formats: " + availableFormatsStr); - return -1; + int renditionChangeIndex = networkRequest.getIndexForFirstEvent(RenditionChangeEvent.TYPE); + if (renditionChangeIndex == -1) { + return -1; } - - void switchRenditionToIndex(int index) { - Format fmt = getAvailableVideoRendition().get(index); - DefaultTrackSelector selector = testActivity.getTrackSelector(); - selector.setParameters(selector.buildUponParameters() - .setMaxVideoSize(fmt.width, fmt.height) - .setForceHighestSupportedBitrate(true) - ); + JSONObject jo = networkRequest.getEventForIndex(renditionChangeIndex); + if (jo == null || !jo.has(VideoData.VIDEO_SOURCE_WIDTH) || !jo + .has(VideoData.VIDEO_SOURCE_HEIGHT)) { + return -1; + } + int videoWidth = jo.getInt(VideoData.VIDEO_SOURCE_WIDTH); + int videoHeight = jo.getInt(VideoData.VIDEO_SOURCE_HEIGHT); + + ArrayList availableFormats = getAvailableVideoRendition(); + int index = 0; + for (Format fmt : availableFormats) { + if (Math.abs(fmt.width - videoWidth) < 10 && + Math.abs(fmt.height - videoHeight) < 10) { + return index; + } + index++; + } + String availableFormatsStr = ""; + for (Format fmt : availableFormats) { + availableFormatsStr += "(" + fmt.width + "x" + fmt.height + ") "; } + fail("Reported source resolution: " + videoWidth + "x" + videoHeight + + " do not match any of available formats: " + availableFormatsStr); + return -1; + } + + void switchRenditionToIndex(int index) { + Format fmt = getAvailableVideoRendition().get(index); + DefaultTrackSelector selector = testActivity.getTrackSelector(); + selector.setParameters(selector.buildUponParameters() + .setMaxVideoSize(fmt.width, fmt.height) + .setForceHighestSupportedBitrate(true) + ); + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/RunInBackgroundTests.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/RunInBackgroundTests.java index 86ed10c7..dd379622 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/RunInBackgroundTests.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/RunInBackgroundTests.java @@ -1,54 +1,52 @@ package com.mux.stats.sdk.muxstats.automatedtests; -import android.os.Bundle; +import static org.junit.Assert.fail; import com.mux.stats.sdk.core.events.playback.PlayEvent; import com.mux.stats.sdk.core.events.playback.PlayingEvent; import com.mux.stats.sdk.core.events.playback.RebufferStartEvent; -import com.mux.stats.sdk.muxstats.automatedtests.ui.SimplePlayerBaseActivity; - import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.fail; - public class RunInBackgroundTests extends TestBase { - @Before - public void init(){ - urlToPlay = "http://localhost:5000/audio.aac"; - super.init(); - } + @Before + public void init() { + urlToPlay = "http://localhost:5000/audio.aac"; + super.init(); + } - @Test - public void testBackgroundAudioPlayback() { - try { - testActivity.waitForActivityToInitialize(); - backgroundActivity(); - testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS); - Thread.sleep(PLAY_PERIOD_IN_MS); - int playingEventIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); - if (playingEventIndex == -1) { - fail("Playing event missing ! Received : " + networkRequest.getReceivedEventNames()); - } - int playEventIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); - if (playEventIndex == -1) { - fail("Play event missing ! Received : " + networkRequest.getReceivedEventNames()); - } - if (playEventIndex > playingEventIndex) { - fail("Play event came after Playing event ! Received : " + networkRequest.getReceivedEventNames()); - } - int rebufferEventIndex = networkRequest.getIndexForFirstEvent(RebufferStartEvent.TYPE); - if (rebufferEventIndex != -1) { - fail("Got rebuffer event on a smooth playback ! Received : " + networkRequest.getReceivedEventNames()); - } - long timeDiff = networkRequest.getCreationTimeForEvent(playEventIndex) - - networkRequest.getCreationTimeForEvent(playingEventIndex); - if (timeDiff > 500) { - fail("Playing event is more then 500 ms apart from play event !!!"); - } - } catch (Exception e) { - fail(getExceptionFullTraceAndMessage( e )); - } + @Test + public void testBackgroundAudioPlayback() { + try { + testActivity.waitForActivityToInitialize(); + backgroundActivity(); + testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS); + Thread.sleep(PLAY_PERIOD_IN_MS); + int playingEventIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); + if (playingEventIndex == -1) { + fail("Playing event missing ! Received : " + networkRequest.getReceivedEventNames()); + } + int playEventIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); + if (playEventIndex == -1) { + fail("Play event missing ! Received : " + networkRequest.getReceivedEventNames()); + } + if (playEventIndex > playingEventIndex) { + fail("Play event came after Playing event ! Received : " + networkRequest + .getReceivedEventNames()); + } + int rebufferEventIndex = networkRequest.getIndexForFirstEvent(RebufferStartEvent.TYPE); + if (rebufferEventIndex != -1) { + fail("Got rebuffer event on a smooth playback ! Received : " + networkRequest + .getReceivedEventNames()); + } + long timeDiff = networkRequest.getCreationTimeForEvent(playEventIndex) - + networkRequest.getCreationTimeForEvent(playingEventIndex); + if (timeDiff > 500) { + fail("Playing event is more then 500 ms apart from play event !!!"); + } + } catch (Exception e) { + fail(getExceptionFullTraceAndMessage(e)); } + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/SeekingTestBase.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/SeekingTestBase.java index 3258bb2f..a8f2b29a 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/SeekingTestBase.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/SeekingTestBase.java @@ -1,5 +1,7 @@ package com.mux.stats.sdk.muxstats.automatedtests; +import static org.junit.Assert.fail; + import com.mux.stats.sdk.core.events.playback.PauseEvent; import com.mux.stats.sdk.core.events.playback.PlayEvent; import com.mux.stats.sdk.core.events.playback.PlayingEvent; @@ -8,107 +10,106 @@ import com.mux.stats.sdk.core.events.playback.SeekedEvent; import com.mux.stats.sdk.core.events.playback.SeekingEvent; -import static org.junit.Assert.fail; - public class SeekingTestBase extends TestBase { - protected void testSeekingWhilePaused() { - try { - if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { - fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); - } - // play x seconds, stage 1 - Thread.sleep(PLAY_PERIOD_IN_MS); - pausePlayer(); - Thread.sleep(PAUSE_PERIOD_IN_MS); - // Seek to the end by triggering touch event - testActivity.runOnUiThread(() -> { - long duration = pView.getPlayer().getDuration(); - pView.getPlayer().seekTo( duration - PLAY_PERIOD_IN_MS ); - }); - Thread.sleep(PLAY_PERIOD_IN_MS); - finishActivity(); - // Expected events play, playing, pause, seeking, seeked - int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE ); - int playingIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE ); - int pauseIndex = networkRequest.getIndexForFirstEvent(PauseEvent.TYPE ); - int seekingIndex = networkRequest.getIndexForFirstEvent(SeekingEvent.TYPE ); - int seekedIndex = networkRequest.getIndexForFirstEvent(SeekedEvent.TYPE ); - if ( ! ( playIndex < playingIndex - && playingIndex < pauseIndex - && pauseIndex < seekingIndex - && seekingIndex < seekedIndex) ) { - fail("Bad event order: playIndex: " + playIndex + ", playingIndex: " + playingIndex - + ", pauseIndex: " + pauseIndex + ", seekingIndex: " + seekingIndex - + ", seekedIndex: " + seekedIndex); - } - playIndex = networkRequest.getIndexForNextEvent( seekedIndex - 1, PlayEvent.TYPE ); - playingIndex = networkRequest.getIndexForNextEvent( seekedIndex - 1, PlayingEvent.TYPE ); - pauseIndex = networkRequest.getIndexForNextEvent( seekedIndex - 1, PauseEvent.TYPE ); - int rebufferStartIndex = networkRequest.getIndexForNextEvent( - seekedIndex - 1, RebufferStartEvent.TYPE ); - int rebufferEndIndex = networkRequest.getIndexForNextEvent( - seekedIndex - 1, RebufferEndEvent.TYPE ); - if ( playIndex != -1 || playingIndex != -1 || pauseIndex != -1 - || rebufferStartIndex != -1 || rebufferEndIndex != -1 ) { - fail( "Seeked event should be last event, found: playIndex: " + playIndex - + ", playingIndex: " + playingIndex + ", pauseIndex: " + pauseIndex - + ", rebufferStartIndex: " + rebufferStartIndex - + ", rebufferEndIndex: " + rebufferEndIndex); - } - } catch (Exception e) { - fail(getExceptionFullTraceAndMessage( e )); - } + protected void testSeekingWhilePaused() { + try { + if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { + fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + // play x seconds, stage 1 + Thread.sleep(PLAY_PERIOD_IN_MS); + pausePlayer(); + Thread.sleep(PAUSE_PERIOD_IN_MS); + // Seek to the end by triggering touch event + testActivity.runOnUiThread(() -> { + long duration = pView.getPlayer().getDuration(); + pView.getPlayer().seekTo(duration - PLAY_PERIOD_IN_MS); + }); + Thread.sleep(PLAY_PERIOD_IN_MS); + finishActivity(); + // Expected events play, playing, pause, seeking, seeked + int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); + int playingIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); + int pauseIndex = networkRequest.getIndexForFirstEvent(PauseEvent.TYPE); + int seekingIndex = networkRequest.getIndexForFirstEvent(SeekingEvent.TYPE); + int seekedIndex = networkRequest.getIndexForFirstEvent(SeekedEvent.TYPE); + if (!(playIndex < playingIndex + && playingIndex < pauseIndex + && pauseIndex < seekingIndex + && seekingIndex < seekedIndex)) { + fail("Bad event order: playIndex: " + playIndex + ", playingIndex: " + playingIndex + + ", pauseIndex: " + pauseIndex + ", seekingIndex: " + seekingIndex + + ", seekedIndex: " + seekedIndex); + } + playIndex = networkRequest.getIndexForNextEvent(seekedIndex - 1, PlayEvent.TYPE); + playingIndex = networkRequest.getIndexForNextEvent(seekedIndex - 1, PlayingEvent.TYPE); + pauseIndex = networkRequest.getIndexForNextEvent(seekedIndex - 1, PauseEvent.TYPE); + int rebufferStartIndex = networkRequest.getIndexForNextEvent( + seekedIndex - 1, RebufferStartEvent.TYPE); + int rebufferEndIndex = networkRequest.getIndexForNextEvent( + seekedIndex - 1, RebufferEndEvent.TYPE); + if (playIndex != -1 || playingIndex != -1 || pauseIndex != -1 + || rebufferStartIndex != -1 || rebufferEndIndex != -1) { + fail("Seeked event should be last event, found: playIndex: " + playIndex + + ", playingIndex: " + playingIndex + ", pauseIndex: " + pauseIndex + + ", rebufferStartIndex: " + rebufferStartIndex + + ", rebufferEndIndex: " + rebufferEndIndex); + } + } catch (Exception e) { + fail(getExceptionFullTraceAndMessage(e)); } + } - protected void testSeekingWhilePlaying() { - try { - if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { - fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); - } - // play x seconds, stage 1 - Thread.sleep(PLAY_PERIOD_IN_MS); - // Seek to the end by triggering touch event - testActivity.runOnUiThread(() -> { - long duration = pView.getPlayer().getDuration(); - pView.getPlayer().seekTo( duration - PLAY_PERIOD_IN_MS ); - }); - Thread.sleep(PLAY_PERIOD_IN_MS); - finishActivity(); - // Expected events play, playing, pause, seeking, seeked - int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE ); - int playingIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE ); - int pauseIndex = networkRequest.getIndexForFirstEvent(PauseEvent.TYPE ); - int seekingIndex = networkRequest.getIndexForFirstEvent(SeekingEvent.TYPE ); - int seekedIndex = networkRequest.getIndexForFirstEvent(SeekedEvent.TYPE ); - if ( ! ( playIndex < playingIndex - && playingIndex < pauseIndex - && pauseIndex < seekingIndex - && seekingIndex < seekedIndex) ) { - fail("Bad event order: playIndex: " + playIndex + ", playingIndex: " + playingIndex - + ", pauseIndex: " + pauseIndex + ", seekingIndex: " + seekingIndex - + ", seekedIndex: " + seekedIndex); - } - playIndex = networkRequest.getIndexForNextEvent( seekedIndex - 1, PlayEvent.TYPE ); - playingIndex = networkRequest.getIndexForNextEvent( seekedIndex - 1, PlayingEvent.TYPE ); - pauseIndex = networkRequest.getIndexForNextEvent( seekedIndex - 1, PauseEvent.TYPE ); - int rebufferStartIndex = networkRequest.getIndexForNextEvent( - seekedIndex - 1, RebufferStartEvent.TYPE ); - int rebufferEndIndex = networkRequest.getIndexForNextEvent( - seekedIndex - 1, RebufferEndEvent.TYPE ); - if ( playIndex != -1 || pauseIndex != -1 || rebufferStartIndex != -1 || rebufferEndIndex != -1 ) { - fail( "Found unwanted events after seeked event: " + seekedIndex - + ", playIndex: " + playIndex - + ", rebufferStartIndex: " + rebufferStartIndex - + ", pauseIndex: " + pauseIndex - + ", rebufferEndIndex: " + rebufferEndIndex); - } - if ( seekedIndex > playingIndex ) { - fail("Missing playing event after seeked event: SeekedEvent: " + seekedIndex - + ", playingIndex: " + playingIndex ); - } - } catch (Exception e) { - fail(getExceptionFullTraceAndMessage( e )); - } + protected void testSeekingWhilePlaying() { + try { + if (!testActivity.waitForPlaybackToStart(waitForPlaybackToStartInMS)) { + fail("Playback did not start in " + waitForPlaybackToStartInMS + " milliseconds !!!"); + } + // play x seconds, stage 1 + Thread.sleep(PLAY_PERIOD_IN_MS); + // Seek to the end by triggering touch event + testActivity.runOnUiThread(() -> { + long duration = pView.getPlayer().getDuration(); + pView.getPlayer().seekTo(duration - PLAY_PERIOD_IN_MS); + }); + Thread.sleep(PLAY_PERIOD_IN_MS); + finishActivity(); + // Expected events play, playing, pause, seeking, seeked + int playIndex = networkRequest.getIndexForFirstEvent(PlayEvent.TYPE); + int playingIndex = networkRequest.getIndexForFirstEvent(PlayingEvent.TYPE); + int pauseIndex = networkRequest.getIndexForFirstEvent(PauseEvent.TYPE); + int seekingIndex = networkRequest.getIndexForFirstEvent(SeekingEvent.TYPE); + int seekedIndex = networkRequest.getIndexForFirstEvent(SeekedEvent.TYPE); + if (!(playIndex < playingIndex + && playingIndex < pauseIndex + && pauseIndex < seekingIndex + && seekingIndex < seekedIndex)) { + fail("Bad event order: playIndex: " + playIndex + ", playingIndex: " + playingIndex + + ", pauseIndex: " + pauseIndex + ", seekingIndex: " + seekingIndex + + ", seekedIndex: " + seekedIndex); + } + playIndex = networkRequest.getIndexForNextEvent(seekedIndex - 1, PlayEvent.TYPE); + playingIndex = networkRequest.getIndexForNextEvent(seekedIndex - 1, PlayingEvent.TYPE); + pauseIndex = networkRequest.getIndexForNextEvent(seekedIndex - 1, PauseEvent.TYPE); + int rebufferStartIndex = networkRequest.getIndexForNextEvent( + seekedIndex - 1, RebufferStartEvent.TYPE); + int rebufferEndIndex = networkRequest.getIndexForNextEvent( + seekedIndex - 1, RebufferEndEvent.TYPE); + if (playIndex != -1 || pauseIndex != -1 || rebufferStartIndex != -1 + || rebufferEndIndex != -1) { + fail("Found unwanted events after seeked event: " + seekedIndex + + ", playIndex: " + playIndex + + ", rebufferStartIndex: " + rebufferStartIndex + + ", pauseIndex: " + pauseIndex + + ", rebufferEndIndex: " + rebufferEndIndex); + } + if (seekedIndex > playingIndex) { + fail("Missing playing event after seeked event: SeekedEvent: " + seekedIndex + + ", playingIndex: " + playingIndex); + } + } catch (Exception e) { + fail(getExceptionFullTraceAndMessage(e)); } + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/TestBase.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/TestBase.java index 06978184..0d17a8ad 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/TestBase.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/TestBase.java @@ -1,26 +1,21 @@ package com.mux.stats.sdk.muxstats.automatedtests; -import android.app.Activity; -import android.app.Instrumentation; -import android.content.Intent; -import android.os.Bundle; -import android.view.KeyEvent; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.fail; +import android.app.Activity; import android.view.MotionEvent; import android.view.View; - -import androidx.test.core.app.ActivityScenario; -import androidx.test.core.app.ApplicationProvider; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.action.MotionEvents; -import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; import androidx.test.runner.lifecycle.Stage; import androidx.test.uiautomator.UiDevice; - -import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.ui.PlayerView; @@ -32,308 +27,301 @@ import com.mux.stats.sdk.muxstats.automatedtests.mockup.MockNetworkRequest; import com.mux.stats.sdk.muxstats.automatedtests.mockup.http.SimpleHTTPServer; import com.mux.stats.sdk.muxstats.automatedtests.ui.SimplePlayerTestActivity; - +import java.io.IOException; +import java.util.Collection; import org.hamcrest.Matcher; import org.json.JSONException; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.rules.TestName; -import org.junit.rules.TestRule; - -import java.io.IOException; -import java.util.Collection; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static org.junit.Assert.fail; public abstract class TestBase { - static final String TAG = "TestBase"; + static final String TAG = "TestBase"; - @Rule - public ActivityTestRule activityRule = - new ActivityTestRule<>(SimplePlayerTestActivity.class); - // This does not work for r2.11.1 flavor + @Rule + public ActivityTestRule activityRule = + new ActivityTestRule<>(SimplePlayerTestActivity.class); + // This does not work for r2.11.1 flavor // public ActivityScenarioRule activityRule = // new ActivityScenarioRule(new Intent( // ApplicationProvider.getApplicationContext(), // SimplePlayerTestActivity.class).putExtras(getActivityOptions()) // ); - @Rule public TestName currentTestName = new TestName(); + @Rule + public TestName currentTestName = new TestName(); - static final int PLAY_PERIOD_IN_MS = 10000; - static final int PAUSE_PERIOD_IN_MS = 3000; - // I could not make this work as expected + static final int PLAY_PERIOD_IN_MS = 10000; + static final int PAUSE_PERIOD_IN_MS = 3000; + // I could not make this work as expected // static final int SEEK_PERIOD_IN_MS = 5000; - protected int runHttpServerOnPort = 5000; - protected int bandwidthLimitInBitsPerSecond = 1500000; - protected int sampleFileBitrate = 1083904; - protected String urlToPlay = "http://localhost:5000/vod.mp4"; - // UTC timestamp whenlow network bandwidth was triggered - long startedJammingTheNetworkAt; - // Amount of video playback time in player buffer - private long bufferedTime; - protected int networkJamPeriodInMs = 10000; - // This is the number of times the network bandwidth will be reduced, - // not constantly but each 10 ms a random number between 2 and factor will divide - // the regular amount of bytes to send. - // This will stop server completly, this will allow us to easier calculate the rebuffer period - protected int networkJamFactor = 4; - protected int waitForPlaybackToStartInMS = 30000; - -// protected ActivityScenario testScenario; - protected SimplePlayerTestActivity testActivity; - protected Activity currentActivity; - protected SimpleHTTPServer httpServer; - protected PlayerView pView; - protected MediaSource testMediaSource; - protected MockNetworkRequest networkRequest; - - protected boolean testActivityFinished; - protected PlayerControlView controlView; - protected View pauseButton; - protected View playButton; - - - @Before - public void init(){ - try { - httpServer = new SimpleHTTPServer(runHttpServerOnPort, bandwidthLimitInBitsPerSecond); + protected int runHttpServerOnPort = 5000; + protected int bandwidthLimitInBitsPerSecond = 1500000; + protected int sampleFileBitrate = 1083904; + protected String urlToPlay = "http://localhost:5000/vod.mp4"; + // UTC timestamp whenlow network bandwidth was triggered + long startedJammingTheNetworkAt; + // Amount of video playback time in player buffer + private long bufferedTime; + protected int networkJamPeriodInMs = 10000; + // This is the number of times the network bandwidth will be reduced, + // not constantly but each 10 ms a random number between 2 and factor will divide + // the regular amount of bytes to send. + // This will stop server completly, this will allow us to easier calculate the rebuffer period + protected int networkJamFactor = 4; + protected int waitForPlaybackToStartInMS = 30000; + + // protected ActivityScenario testScenario; + protected SimplePlayerTestActivity testActivity; + protected Activity currentActivity; + protected SimpleHTTPServer httpServer; + protected PlayerView pView; + protected MediaSource testMediaSource; + protected MockNetworkRequest networkRequest; + + protected boolean testActivityFinished; + protected PlayerControlView controlView; + protected View pauseButton; + protected View playButton; + + + @Before + public void init() { + try { + httpServer = new SimpleHTTPServer(runHttpServerOnPort, bandwidthLimitInBitsPerSecond); // httpServer.setSeekLatency(SEEK_PERIOD_IN_MS); - } catch (IOException e) { - e.printStackTrace(); - // Failed to start server - fail("Failed to start HTTP server, why !!!"); - } - try { - testActivity = (SimplePlayerTestActivity) getActivityInstance(); - } catch (ClassCastException e) { - fail("Got wrong activity instance in test init !!!"); - } - if (testActivity == null) { - fail("Test activity not found !!!"); - } - testActivityFinished = false; - testActivity.runOnUiThread(() -> { - testActivity.setVideoTitle( BuildConfig.FLAVOR + "-" + currentTestName.getMethodName()); - testActivity.setUrlToPlay(urlToPlay); - testActivity.initMuxSats(); - testActivity.startPlayback(); - pView = testActivity.getPlayerView(); - testMediaSource = testActivity.getTestMediaSource(); - networkRequest = testActivity.getMockNetwork(); - }); -// testScenario = activityRule.getScenario(); + } catch (IOException e) { + e.printStackTrace(); + // Failed to start server + fail("Failed to start HTTP server, why !!!"); } - - @After - public void cleanup() { - if (httpServer != null) { - httpServer.kill(); - } - finishActivity(); -// testScenario.close(); + try { + testActivity = (SimplePlayerTestActivity) getActivityInstance(); + } catch (ClassCastException e) { + fail("Got wrong activity instance in test init !!!"); } - -// public abstract Bundle getActivityOptions(); - - public void jamNetwork() { - testActivity.runOnUiThread(() -> { - startedJammingTheNetworkAt = System.currentTimeMillis(); - long bufferPosition = pView.getPlayer().getBufferedPosition(); - long currentPosition = pView.getPlayer().getCurrentPosition(); - bufferedTime = bufferPosition - currentPosition; - httpServer.jamNetwork(networkJamPeriodInMs, networkJamFactor, true); - }); - } - - public void exitActivity() { - testActivity.runOnUiThread(() -> testActivity.finish()); + if (testActivity == null) { + fail("Test activity not found !!!"); } + testActivityFinished = false; + testActivity.runOnUiThread(() -> { + testActivity.setVideoTitle(BuildConfig.FLAVOR + "-" + currentTestName.getMethodName()); + testActivity.setUrlToPlay(urlToPlay); + testActivity.initMuxSats(); + testActivity.startPlayback(); + pView = testActivity.getPlayerView(); + testMediaSource = testActivity.getTestMediaSource(); + networkRequest = testActivity.getMockNetwork(); + }); +// testScenario = activityRule.getScenario(); + } - public CheckupResult checkPlaybackPeriodAtIndex(int index, long expectedPlayPeriod) - throws JSONException { - CheckupResult result = new CheckupResult(); - int playIndex = networkRequest.getIndexForNextEvent(index, PlayEvent.TYPE); - int playingIndex = networkRequest.getIndexForNextEvent(index, PlayingEvent.TYPE); - int pauseIndex = networkRequest.getIndexForNextEvent(index, PauseEvent.TYPE); - // Play index not necessary - if ( pauseIndex == -1 || playingIndex == -1) { - fail("Missing basic playback events, playIndex: " - + playIndex + ", playingIndex: " + playingIndex + - ", pauseIndex: " + pauseIndex + ", starting at index: " + index + - ", availableEvents: " + networkRequest.getReceivedEventNames()); - } - if (!((playIndex < playingIndex) && (playingIndex < pauseIndex))) { - fail("Basic playback events not ordered correctly, playIndex: " - + playIndex + ", playingIndex: " + playingIndex + - ", pauseIndex: " + pauseIndex + ", starting at index: " + index + - ", availableEvents: " + networkRequest.getReceivedEventNames()); - } - long playbackPeriod = networkRequest.getCreationTimeForEvent(pauseIndex) - - networkRequest.getCreationTimeForEvent(playingIndex); - if (Math.abs(playbackPeriod - expectedPlayPeriod) > 500 ) { - fail("Reported play period: " + playbackPeriod + " do not match expected play period: " - + expectedPlayPeriod + ", playingIndex: " + playingIndex + - ", pauseIndex: " + pauseIndex + ", starting at event index: " + index + - ", availableEvents: " + networkRequest.getReceivedEventNames()); - } - result.eventIndex = pauseIndex; - result.playbackPeriod = playbackPeriod; - return result; + @After + public void cleanup() { + if (httpServer != null) { + httpServer.kill(); } + finishActivity(); +// testScenario.close(); + } - public CheckupResult checkPausePeriodAtIndex(int index, long expectedPausePeriod) - throws JSONException { - CheckupResult result = new CheckupResult(); - int playIndex = networkRequest.getIndexForNextEvent(index, PlayEvent.TYPE); - int playingIndex = networkRequest.getIndexForNextEvent(index, PlayingEvent.TYPE); - int pauseIndex = networkRequest.getIndexForNextEvent(index, PauseEvent.TYPE); - if (playIndex == -1 || pauseIndex == -1 || playingIndex == -1) { - fail("Missing basic playback events, playIndex: " - + playIndex + ", playingIndex: " + playingIndex + - ", pauseIndex: " + pauseIndex + ", starting at index: " + index + - ", availableEvents: " + networkRequest.getReceivedEventNames()); - } - if (!((playIndex < playingIndex) && (playIndex > pauseIndex))) { - fail("Basic playback events not ordered correctly, pauseIndex: " - + pauseIndex + ", playIndex: " + playIndex + - ", playingIndex: " + playingIndex + ", starting at index: " + index + - ", availableEvents: " + networkRequest.getReceivedEventNames()); - } - long pausekPeriod = networkRequest.getCreationTimeForEvent(playIndex) - - networkRequest.getCreationTimeForEvent(pauseIndex); - if (Math.abs(pausekPeriod - expectedPausePeriod) > 500 ) { - fail("Reported pause period: " + pausekPeriod + " do not match expected play period: " - + expectedPausePeriod + ", playIndex: " + playIndex + - ", pauseIndex: " + pauseIndex + ", starting at event index: " + index + - ", availableEvents: " + networkRequest.getReceivedEventNames()); - } - result.pausePeriod = pausekPeriod; - result.eventIndex = playingIndex; - return result; - } +// public abstract Bundle getActivityOptions(); - public CheckupResult checkSeekAtIndex(int index) throws JSONException { - CheckupResult result = new CheckupResult(); - int seekingIndex = networkRequest.getIndexForNextEvent(index, SeekingEvent.TYPE); - int seekedIndex = networkRequest.getIndexForNextEvent(index, SeekedEvent.TYPE); - if (seekingIndex == -1 || seekedIndex == -1) { - fail("Missing seeekingEvent or seekEvent at event index: " + index + - ", availableEvents: " + networkRequest.getReceivedEventNames()); - } - if (seekedIndex < seekingIndex) { - fail("seeked event is preceding the seeking event at event index: " + index + - ", availableEvents: " + networkRequest.getReceivedEventNames()); - } - result.eventIndex = seekedIndex; - result.seekPeriod = networkRequest.getCreationTimeForEvent(seekedIndex) - - networkRequest.getCreationTimeForEvent(seekingIndex); - return result; + public void jamNetwork() { + testActivity.runOnUiThread(() -> { + startedJammingTheNetworkAt = System.currentTimeMillis(); + long bufferPosition = pView.getPlayer().getBufferedPosition(); + long currentPosition = pView.getPlayer().getCurrentPosition(); + bufferedTime = bufferPosition - currentPosition; + httpServer.jamNetwork(networkJamPeriodInMs, networkJamFactor, true); + }); + } + + public void exitActivity() { + testActivity.runOnUiThread(() -> testActivity.finish()); + } + + public CheckupResult checkPlaybackPeriodAtIndex(int index, long expectedPlayPeriod) + throws JSONException { + CheckupResult result = new CheckupResult(); + int playIndex = networkRequest.getIndexForNextEvent(index, PlayEvent.TYPE); + int playingIndex = networkRequest.getIndexForNextEvent(index, PlayingEvent.TYPE); + int pauseIndex = networkRequest.getIndexForNextEvent(index, PauseEvent.TYPE); + // Play index not necessary + if (pauseIndex == -1 || playingIndex == -1) { + fail("Missing basic playback events, playIndex: " + + playIndex + ", playingIndex: " + playingIndex + + ", pauseIndex: " + pauseIndex + ", starting at index: " + index + + ", availableEvents: " + networkRequest.getReceivedEventNames()); } - - public void pausePlayer() { - // Pause video - testActivity.runOnUiThread(() -> { - if (pauseButton != null) { - pauseButton.performClick(); - } else { - pView.getPlayer().setPlayWhenReady(false); - } - }); + if (!((playIndex < playingIndex) && (playingIndex < pauseIndex))) { + fail("Basic playback events not ordered correctly, playIndex: " + + playIndex + ", playingIndex: " + playingIndex + + ", pauseIndex: " + pauseIndex + ", starting at index: " + index + + ", availableEvents: " + networkRequest.getReceivedEventNames()); } - - public void resumePlayer() { - testActivity.runOnUiThread(() -> { - if (playButton != null) { - playButton.performClick(); - } else { - pView.getPlayer().setPlayWhenReady(true); - } - }); + long playbackPeriod = networkRequest.getCreationTimeForEvent(pauseIndex) - + networkRequest.getCreationTimeForEvent(playingIndex); + if (Math.abs(playbackPeriod - expectedPlayPeriod) > 500) { + fail("Reported play period: " + playbackPeriod + " do not match expected play period: " + + expectedPlayPeriod + ", playingIndex: " + playingIndex + + ", pauseIndex: " + pauseIndex + ", starting at event index: " + index + + ", availableEvents: " + networkRequest.getReceivedEventNames()); } - - public void backgroundActivity() { - UiDevice device = UiDevice.getInstance(getInstrumentation()); - device.pressHome(); + result.eventIndex = pauseIndex; + result.playbackPeriod = playbackPeriod; + return result; + } + + public CheckupResult checkPausePeriodAtIndex(int index, long expectedPausePeriod) + throws JSONException { + CheckupResult result = new CheckupResult(); + int playIndex = networkRequest.getIndexForNextEvent(index, PlayEvent.TYPE); + int playingIndex = networkRequest.getIndexForNextEvent(index, PlayingEvent.TYPE); + int pauseIndex = networkRequest.getIndexForNextEvent(index, PauseEvent.TYPE); + if (playIndex == -1 || pauseIndex == -1 || playingIndex == -1) { + fail("Missing basic playback events, playIndex: " + + playIndex + ", playingIndex: " + playingIndex + + ", pauseIndex: " + pauseIndex + ", starting at index: " + index + + ", availableEvents: " + networkRequest.getReceivedEventNames()); } - - public void finishActivity() { - try { - if (!testActivityFinished && testActivity != null) { - testActivity.finish(); - testActivityFinished = true; - } - } catch ( Exception e ) { - e.printStackTrace(); - } + if (!((playIndex < playingIndex) && (playIndex > pauseIndex))) { + fail("Basic playback events not ordered correctly, pauseIndex: " + + pauseIndex + ", playIndex: " + playIndex + + ", playingIndex: " + playingIndex + ", starting at index: " + index + + ", availableEvents: " + networkRequest.getReceivedEventNames()); } - - protected Activity getActivityInstance(){ - getInstrumentation().runOnMainSync(() -> { - Collection resumedActivities = - ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED); - for (Activity activity: resumedActivities){ - currentActivity = activity; - break; - } - }); - - return currentActivity; + long pausekPeriod = networkRequest.getCreationTimeForEvent(playIndex) - + networkRequest.getCreationTimeForEvent(pauseIndex); + if (Math.abs(pausekPeriod - expectedPausePeriod) > 500) { + fail("Reported pause period: " + pausekPeriod + " do not match expected play period: " + + expectedPausePeriod + ", playIndex: " + playIndex + + ", pauseIndex: " + pauseIndex + ", starting at event index: " + index + + ", availableEvents: " + networkRequest.getReceivedEventNames()); } - - protected String getExceptionFullTraceAndMessage(Exception e) { - String lStackTraceString = ""; - for (StackTraceElement lStEl : e.getStackTrace() ) { - lStackTraceString += lStEl.toString() + "\n"; - } - lStackTraceString += e.getMessage(); - return lStackTraceString; + result.pausePeriod = pausekPeriod; + result.eventIndex = playingIndex; + return result; + } + + public CheckupResult checkSeekAtIndex(int index) throws JSONException { + CheckupResult result = new CheckupResult(); + int seekingIndex = networkRequest.getIndexForNextEvent(index, SeekingEvent.TYPE); + int seekedIndex = networkRequest.getIndexForNextEvent(index, SeekedEvent.TYPE); + if (seekingIndex == -1 || seekedIndex == -1) { + fail("Missing seeekingEvent or seekEvent at event index: " + index + + ", availableEvents: " + networkRequest.getReceivedEventNames()); } - - public void triggerTouchEvent( float x, float y ) { - onView(withId(R.id.player_view)).perform(touchDownAndUpAction(x, y)); + if (seekedIndex < seekingIndex) { + fail("seeked event is preceding the seeking event at event index: " + index + + ", availableEvents: " + networkRequest.getReceivedEventNames()); } - - public static ViewAction touchDownAndUpAction(final float x, final float y) { - return new ViewAction() { - @Override - public Matcher getConstraints() { - return isDisplayed(); - } - - @Override - public String getDescription() { - return "Send touch events."; - } - - @Override - public void perform(UiController uiController, final View view) { - // Get view absolute position - int[] location = new int[2]; - view.getLocationOnScreen(location); - - // Offset coordinates by view position - float[] coordinates = new float[] { x + location[0], y + location[1] }; - float[] precision = new float[] { 1f, 1f }; - - // Send down event, pause, and send up - MotionEvent down = MotionEvents.sendDown(uiController, coordinates, precision).down; - uiController.loopMainThreadForAtLeast(200); - MotionEvents.sendUp(uiController, down, coordinates); - } - }; + result.eventIndex = seekedIndex; + result.seekPeriod = networkRequest.getCreationTimeForEvent(seekedIndex) - + networkRequest.getCreationTimeForEvent(seekingIndex); + return result; + } + + public void pausePlayer() { + // Pause video + testActivity.runOnUiThread(() -> { + if (pauseButton != null) { + pauseButton.performClick(); + } else { + pView.getPlayer().setPlayWhenReady(false); + } + }); + } + + public void resumePlayer() { + testActivity.runOnUiThread(() -> { + if (playButton != null) { + playButton.performClick(); + } else { + pView.getPlayer().setPlayWhenReady(true); + } + }); + } + + public void backgroundActivity() { + UiDevice device = UiDevice.getInstance(getInstrumentation()); + device.pressHome(); + } + + public void finishActivity() { + try { + if (!testActivityFinished && testActivity != null) { + testActivity.finish(); + testActivityFinished = true; + } + } catch (Exception e) { + e.printStackTrace(); } - - class CheckupResult { - int eventIndex; - long pausePeriod; - long seekPeriod; - long playbackPeriod; + } + + protected Activity getActivityInstance() { + getInstrumentation().runOnMainSync(() -> { + Collection resumedActivities = + ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED); + for (Activity activity : resumedActivities) { + currentActivity = activity; + break; + } + }); + + return currentActivity; + } + + protected String getExceptionFullTraceAndMessage(Exception e) { + String lStackTraceString = ""; + for (StackTraceElement lStEl : e.getStackTrace()) { + lStackTraceString += lStEl.toString() + "\n"; } + lStackTraceString += e.getMessage(); + return lStackTraceString; + } + + public void triggerTouchEvent(float x, float y) { + onView(withId(R.id.player_view)).perform(touchDownAndUpAction(x, y)); + } + + public static ViewAction touchDownAndUpAction(final float x, final float y) { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return isDisplayed(); + } + + @Override + public String getDescription() { + return "Send touch events."; + } + + @Override + public void perform(UiController uiController, final View view) { + // Get view absolute position + int[] location = new int[2]; + view.getLocationOnScreen(location); + + // Offset coordinates by view position + float[] coordinates = new float[]{x + location[0], y + location[1]}; + float[] precision = new float[]{1f, 1f}; + + // Send down event, pause, and send up + MotionEvent down = MotionEvents.sendDown(uiController, coordinates, precision).down; + uiController.loopMainThreadForAtLeast(200); + MotionEvents.sendUp(uiController, down, coordinates); + } + }; + } + + class CheckupResult { + + int eventIndex; + long pausePeriod; + long seekPeriod; + long playbackPeriod; + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/MockNetworkRequest.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/MockNetworkRequest.java index e7da2892..6150cd48 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/MockNetworkRequest.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/MockNetworkRequest.java @@ -1,130 +1,129 @@ package com.mux.stats.sdk.muxstats.automatedtests.mockup; -import com.mux.stats.sdk.muxstats.automatedtests.BuildConfig; import com.mux.stats.sdk.muxstats.INetworkRequest; import com.mux.stats.sdk.muxstats.MuxNetworkRequests; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - +import com.mux.stats.sdk.muxstats.automatedtests.BuildConfig; import java.net.URL; import java.util.ArrayList; import java.util.Hashtable; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; public class MockNetworkRequest implements INetworkRequest { - IMuxNetworkRequestsCompletion callback; - ArrayList receivedEvents = new ArrayList<>(); - MuxNetworkRequests muxNetwork; - IMuxNetworkRequestsCompletion muxNetworkCallback = new IMuxNetworkRequestsCompletion() { - - @Override - public void onComplete(boolean b) { - // Do nothing - } - }; - - public MockNetworkRequest() { - muxNetwork = new MuxNetworkRequests(); - } - - public void sendResponse(boolean shouldSucceed) { - callback.onComplete(shouldSucceed); - } + IMuxNetworkRequestsCompletion callback; + ArrayList receivedEvents = new ArrayList<>(); + MuxNetworkRequests muxNetwork; + IMuxNetworkRequestsCompletion muxNetworkCallback = new IMuxNetworkRequestsCompletion() { @Override - public void get(URL url) { - System.out.println("GET: " + url); + public void onComplete(boolean b) { + // Do nothing } - - @Override - public void post(URL url, JSONObject body, Hashtable headers) { - System.out.println("POST: " + url + ", body: " + body.toString()); - // TODO parse these requests to an events + }; + + + public MockNetworkRequest() { + muxNetwork = new MuxNetworkRequests(); + } + + public void sendResponse(boolean shouldSucceed) { + callback.onComplete(shouldSucceed); + } + + @Override + public void get(URL url) { + System.out.println("GET: " + url); + } + + @Override + public void post(URL url, JSONObject body, Hashtable headers) { + System.out.println("POST: " + url + ", body: " + body.toString()); + // TODO parse these requests to an events + } + + @Override + public void postWithCompletion(String envKey, String body, + Hashtable headers, + IMuxNetworkRequestsCompletion callback) { + try { + JSONObject bodyJo = new JSONObject(body); + JSONArray events = bodyJo.getJSONArray("events"); + for (int i = 0; i < events.length(); i++) { + JSONObject eventJo = events.getJSONObject(i); + receivedEvents.add(eventJo); + } + System.out.println("Mock network postWithCompletion called !!!"); + this.callback = callback; + // For now always simulate a successful report + callback.onComplete(true); + } catch (JSONException e) { + e.printStackTrace(); } - - @Override - public void postWithCompletion(String envKey, String body, - Hashtable headers, - IMuxNetworkRequestsCompletion callback) { - try { - JSONObject bodyJo = new JSONObject(body); - JSONArray events = bodyJo.getJSONArray("events"); - for (int i = 0; i < events.length(); i++) { - JSONObject eventJo = events.getJSONObject(i); - receivedEvents.add(eventJo); - } - System.out.println("Mock network postWithCompletion called !!!"); - this.callback = callback; - // For now always simulate a successful report - callback.onComplete(true); - } catch (JSONException e) { - e.printStackTrace(); - } - if (BuildConfig.SHOULD_REPORT_INSTRUMENTATION_TEST_EVENTS_TO_SERVER) { - // Send events to actual server - muxNetwork.postWithCompletion(envKey, body, headers, muxNetworkCallback); - } + if (BuildConfig.SHOULD_REPORT_INSTRUMENTATION_TEST_EVENTS_TO_SERVER) { + // Send events to actual server + muxNetwork.postWithCompletion(envKey, body, headers, muxNetworkCallback); } + } - public String getReceivedEventName(int index) throws JSONException { - if (index > receivedEvents.size()) { - return null; - } - return receivedEvents.get(index).getString("e"); + public String getReceivedEventName(int index) throws JSONException { + if (index > receivedEvents.size()) { + return null; } - - public int getIndexForFirstEvent(String eventName) throws JSONException { - for (int i = 0; i < receivedEvents.size(); i ++) { - if(getReceivedEventName(i).equals(eventName)) { - return i; - } - } - return -1; + return receivedEvents.get(index).getString("e"); + } + + public int getIndexForFirstEvent(String eventName) throws JSONException { + for (int i = 0; i < receivedEvents.size(); i++) { + if (getReceivedEventName(i).equals(eventName)) { + return i; + } } - - public int getIndexForNextEvent(int startingIndex, String eventName) throws JSONException { - for (int i = startingIndex; i < receivedEvents.size(); i ++) { - if(getReceivedEventName(i).equals(eventName)) { - return i; - } - } - return -1; + return -1; + } + + public int getIndexForNextEvent(int startingIndex, String eventName) throws JSONException { + for (int i = startingIndex; i < receivedEvents.size(); i++) { + if (getReceivedEventName(i).equals(eventName)) { + return i; + } } - - public int getIndexForLastEvent(String eventName) throws JSONException { - for (int i = receivedEvents.size() - 1; i >= 0; i --) { - if(getReceivedEventName(i).equals(eventName)) { - return i; - } - } - return -1; + return -1; + } + + public int getIndexForLastEvent(String eventName) throws JSONException { + for (int i = receivedEvents.size() - 1; i >= 0; i--) { + if (getReceivedEventName(i).equals(eventName)) { + return i; + } } + return -1; + } - public JSONObject getEventForIndex(int index) { - if ( index == -1 || index >= receivedEvents.size()) { - return null; - } - return receivedEvents.get(index); + public JSONObject getEventForIndex(int index) { + if (index == -1 || index >= receivedEvents.size()) { + return null; } + return receivedEvents.get(index); + } - public ArrayList getReceivedEventNames() throws JSONException { - ArrayList eventNames = new ArrayList<>(); - for (int i =0; i < receivedEvents.size(); i++) { - eventNames.add(getReceivedEventName(i)); - } - return eventNames; + public ArrayList getReceivedEventNames() throws JSONException { + ArrayList eventNames = new ArrayList<>(); + for (int i = 0; i < receivedEvents.size(); i++) { + eventNames.add(getReceivedEventName(i)); } + return eventNames; + } - public int getNumberOfReceivedEvents() { - return receivedEvents.size(); - } + public int getNumberOfReceivedEvents() { + return receivedEvents.size(); + } - public long getCreationTimeForEvent(int index) throws JSONException { - if (index > receivedEvents.size() || index < 0 ) { - return -1; - } - return receivedEvents.get(index).getLong("uti"); + public long getCreationTimeForEvent(int index) throws JSONException { + if (index > receivedEvents.size() || index < 0) { + return -1; } + return receivedEvents.get(index).getLong("uti"); + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionReceiver.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionReceiver.java index f9f3ff15..98a74df5 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionReceiver.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionReceiver.java @@ -1,101 +1,99 @@ package com.mux.stats.sdk.muxstats.automatedtests.mockup.http; import android.util.Log; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.LinkedBlockingDeque; public class ConnectionReceiver extends Thread { - static final String TAG = "HTTPTest"; + static final String TAG = "HTTPTest"; - boolean isRunning; - String serveAssetFile = "sample.mp4"; - InputStream httpInput; - HttpRequestParser httpParser = new HttpRequestParser(); - BufferedReader reader; - LinkedBlockingDeque actions = new LinkedBlockingDeque<>(100); + boolean isRunning; + String serveAssetFile = "sample.mp4"; + InputStream httpInput; + HttpRequestParser httpParser = new HttpRequestParser(); + BufferedReader reader; + LinkedBlockingDeque actions = new LinkedBlockingDeque<>(100); - public ConnectionReceiver(InputStream httpInput) { - this.httpInput = httpInput; - reader = new BufferedReader(new InputStreamReader(httpInput)); - } + public ConnectionReceiver(InputStream httpInput) { + this.httpInput = httpInput; + reader = new BufferedReader(new InputStreamReader(httpInput)); + } - public void kill() { - isRunning = false; - interrupt(); - } + public void kill() { + isRunning = false; + interrupt(); + } - public ServerAction getNextAction() throws InterruptedException { - return actions.take(); - } + public ServerAction getNextAction() throws InterruptedException { + return actions.take(); + } - public void run() { - isRunning = true; - while (isRunning) { - try { - String httpRequest = waitForRequest(); - if (httpRequest == null || httpRequest.length() == 0) { - sleep(50); - } else { - Log.w(TAG, "Received request:\n" + httpRequest); - processRequest(httpRequest + "\r\n"); - } - } catch (IOException e) { - // Connection closed - isRunning = false; - } catch (InterruptedException e) { - // Someone killed the thread - isRunning = false; - } catch (HttpFormatException e) { - // TODO handle this better - e.printStackTrace(); - } + public void run() { + isRunning = true; + while (isRunning) { + try { + String httpRequest = waitForRequest(); + if (httpRequest == null || httpRequest.length() == 0) { + sleep(50); + } else { + Log.w(TAG, "Received request:\n" + httpRequest); + processRequest(httpRequest + "\r\n"); } + } catch (IOException e) { + // Connection closed + isRunning = false; + } catch (InterruptedException e) { + // Someone killed the thread + isRunning = false; + } catch (HttpFormatException e) { + // TODO handle this better + e.printStackTrace(); + } } + } - /* - * Wait for http request - */ - private String waitForRequest() throws IOException { - StringBuilder total = new StringBuilder(); - String line = reader.readLine(); - while (line != null && line.length() > 0) { - total.append(line).append('\n'); - line = reader.readLine(); - } - return (total.toString()); + /* + * Wait for http request + */ + private String waitForRequest() throws IOException { + StringBuilder total = new StringBuilder(); + String line = reader.readLine(); + while (line != null && line.length() > 0) { + total.append(line).append('\n'); + line = reader.readLine(); } + return (total.toString()); + } - /* - * Parse HTTP request - */ - private void processRequest(String httpRequest) throws - IOException, HttpFormatException, InterruptedException { + /* + * Parse HTTP request + */ + private void processRequest(String httpRequest) throws + IOException, HttpFormatException, InterruptedException { - httpParser.parseRequest(httpRequest); - String requestLine = httpParser.getRequestLine(); - String assetFileName = requestLine.split("HTTP")[0]; - assetFileName = assetFileName.replace("GET /", ""); - assetFileName = assetFileName.trim(); - serveAssetFile = assetFileName; - HashMap headers = new HashMap(); - int range = 0; - String rangeHeader = httpParser.getHeaderParam("Range"); - String originHeader = httpParser.getHeaderParam("Origin"); - if (originHeader != null) { - headers.put("Origin", originHeader); - } - if (rangeHeader != null) { - headers.put("Range", rangeHeader); - } - actions.put(new ServerAction(ServerAction.SERVE_MEDIA_DATA, headers, serveAssetFile)); -// actions.add(new ServerAction(ServerAction.SERVE_MEDIA_DATA)); + httpParser.parseRequest(httpRequest); + String requestLine = httpParser.getRequestLine(); + String assetFileName = requestLine.split("HTTP")[0]; + assetFileName = assetFileName.replace("GET /", ""); + assetFileName = assetFileName.trim(); + serveAssetFile = assetFileName; + HashMap headers = new HashMap(); + int range = 0; + String rangeHeader = httpParser.getHeaderParam("Range"); + String originHeader = httpParser.getHeaderParam("Origin"); + if (originHeader != null) { + headers.put("Origin", originHeader); + } + if (rangeHeader != null) { + headers.put("Range", rangeHeader); } + actions.put(new ServerAction(ServerAction.SERVE_MEDIA_DATA, headers, serveAssetFile)); +// actions.add(new ServerAction(ServerAction.SERVE_MEDIA_DATA)); + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionSender.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionSender.java index cc999e1c..89cc5829 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionSender.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionSender.java @@ -2,278 +2,275 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.HashMap; import java.util.Random; public class ConnectionSender extends Thread { - static final String TAG = "HTTPTestConnSender"; + static final String TAG = "HTTPTestConnSender"; - OutputStream httpOut; - InputStream assetInput; - Context context; - boolean isPaused; - boolean isRunning; + OutputStream httpOut; + InputStream assetInput; + Context context; + boolean isPaused; + boolean isRunning; - long networkJammingEndPeriod = -1; - int networkJamFactor = 1; - boolean constantJam = false; - Random r = new Random(); + long networkJammingEndPeriod = -1; + int networkJamFactor = 1; + boolean constantJam = false; + Random r = new Random(); - long assetFileSize; - long serveDataFromPosition; - String originHeaderValue; - long previouseDataPositionRequested; - long bandwidthLimit; - long seekLatency; - boolean seekLatencyServed = false; - byte[] transferBuffer; - int transferBufferSize; + long assetFileSize; + long serveDataFromPosition; + String originHeaderValue; + long previouseDataPositionRequested; + long bandwidthLimit; + long seekLatency; + boolean seekLatencyServed = false; + byte[] transferBuffer; + int transferBufferSize; - public ConnectionSender(OutputStream httpOut, int bandwidthLimit, - long networkJammingEndPeriod, int networkJamFactor, - long seekLatency) throws IOException { - this.httpOut = httpOut; - this.bandwidthLimit = bandwidthLimit; - this.networkJammingEndPeriod = networkJammingEndPeriod; - this.networkJamFactor = networkJamFactor; - this.seekLatency = seekLatency; - previouseDataPositionRequested = -1; + public ConnectionSender(OutputStream httpOut, int bandwidthLimit, + long networkJammingEndPeriod, int networkJamFactor, + long seekLatency) throws IOException { + this.httpOut = httpOut; + this.bandwidthLimit = bandwidthLimit; + this.networkJammingEndPeriod = networkJammingEndPeriod; + this.networkJamFactor = networkJamFactor; + this.seekLatency = seekLatency; + previouseDataPositionRequested = -1; - transferBufferSize = bandwidthLimit / (8 * 100); - transferBuffer = new byte[transferBufferSize]; // Max number of bytes to send each 10 ms - isPaused = true; - start(); - } + transferBufferSize = bandwidthLimit / (8 * 100); + transferBuffer = new byte[transferBufferSize]; // Max number of bytes to send each 10 ms + isPaused = true; + start(); + } - public void kill() { - isRunning = false; - interrupt(); - } + public void kill() { + isRunning = false; + interrupt(); + } - public void pause() { - isPaused = true; - } + public void pause() { + isPaused = true; + } - private void parseRangeHeader(HashMap headers) { - this.serveDataFromPosition = 0; - for (String headerName : headers.keySet()) { - if (headerName.equalsIgnoreCase("Range")) { - this.serveDataFromPosition = - Integer.valueOf(headers.get(headerName).replaceAll("[^0-9]", "")); - Log.i(TAG, "Got range header value: " + this.serveDataFromPosition); - } - } + private void parseRangeHeader(HashMap headers) { + this.serveDataFromPosition = 0; + for (String headerName : headers.keySet()) { + if (headerName.equalsIgnoreCase("Range")) { + this.serveDataFromPosition = + Integer.valueOf(headers.get(headerName).replaceAll("[^0-9]", "")); + Log.i(TAG, "Got range header value: " + this.serveDataFromPosition); + } } + } - private void parseOriginHeader(HashMap headers) { - this.originHeaderValue = ""; - for (String headerName : headers.keySet()) { - if (headerName.equalsIgnoreCase("Origin")) { - this.originHeaderValue = headers.get(headerName); - Log.i(TAG, "Got range header value: " + this.serveDataFromPosition); - } - } + private void parseOriginHeader(HashMap headers) { + this.originHeaderValue = ""; + for (String headerName : headers.keySet()) { + if (headerName.equalsIgnoreCase("Origin")) { + this.originHeaderValue = headers.get(headerName); + Log.i(TAG, "Got range header value: " + this.serveDataFromPosition); + } } + } - public void startServingFromPosition(String assetName, HashMap headers) - throws IOException, InterruptedException { - parseRangeHeader(headers); - parseOriginHeader(headers); - boolean sendPartialResponse = true; - boolean acceptRangeHeader = true; - String contentType = "video/mp4"; - if (assetName.contains(".xml")) { - contentType = "text/xml"; - sendPartialResponse = false; - } else if (assetName.contains(".m3u8")) { - contentType = "application/x-mpegURL"; - sendPartialResponse = false; - } else if (assetName.contains(".ts")) { - contentType = "video/mp2t"; - acceptRangeHeader = false; - } else if (assetName.contains(".aac")) { - contentType = "audio/aac"; - } else if (assetName.contains(".png")) { - contentType = "image/png"; - sendPartialResponse = false; - } - openAssetFile(assetName); - assetInput.reset(); - assetInput.skip(serveDataFromPosition); - Log.i(TAG, "Serving file from position: " + serveDataFromPosition + ", remaining bytes: " + - assetInput.available() + ", total file size: " + assetFileSize); - if (serveDataFromPosition < assetFileSize) { - if (sendPartialResponse) { - sendHTTPOKPartialResponse(contentType, acceptRangeHeader); - isPaused = false; - } else { - // Send complete response,, this is a short file - sendHTTPOKCompleteResponse(contentType); - } - } else { - sendRequestedRangeNotSatisfiable(); - } + public void startServingFromPosition(String assetName, HashMap headers) + throws IOException, InterruptedException { + parseRangeHeader(headers); + parseOriginHeader(headers); + boolean sendPartialResponse = true; + boolean acceptRangeHeader = true; + String contentType = "video/mp4"; + if (assetName.contains(".xml")) { + contentType = "text/xml"; + sendPartialResponse = false; + } else if (assetName.contains(".m3u8")) { + contentType = "application/x-mpegURL"; + sendPartialResponse = false; + } else if (assetName.contains(".ts")) { + contentType = "video/mp2t"; + acceptRangeHeader = false; + } else if (assetName.contains(".aac")) { + contentType = "audio/aac"; + } else if (assetName.contains(".png")) { + contentType = "image/png"; + sendPartialResponse = false; } - - public void jamNetwork(long jamPeriod, int jamFactor, boolean constantJam) { - networkJammingEndPeriod = System.currentTimeMillis() + jamPeriod; - this.networkJamFactor = jamFactor; - this.constantJam = constantJam; + openAssetFile(assetName); + assetInput.reset(); + assetInput.skip(serveDataFromPosition); + Log.i(TAG, "Serving file from position: " + serveDataFromPosition + ", remaining bytes: " + + assetInput.available() + ", total file size: " + assetFileSize); + if (serveDataFromPosition < assetFileSize) { + if (sendPartialResponse) { + sendHTTPOKPartialResponse(contentType, acceptRangeHeader); + isPaused = false; + } else { + // Send complete response,, this is a short file + sendHTTPOKCompleteResponse(contentType); + } + } else { + sendRequestedRangeNotSatisfiable(); } + } - public void run() { - isRunning = true; - while (isRunning) { - try { - if (!isPaused) { - // Serve static data only once !!! - serveStaticData(); - } else { - sleep(5); - } - } catch (InterruptedException e) { - // Thread killed - } catch (IOException e) { - e.printStackTrace(); - Log.e(TAG, "Connection closed by the client !!!"); - isRunning = false; - } - } - } + public void jamNetwork(long jamPeriod, int jamFactor, boolean constantJam) { + networkJammingEndPeriod = System.currentTimeMillis() + jamPeriod; + this.networkJamFactor = jamFactor; + this.constantJam = constantJam; + } - private void openAssetFile(String assetFile) throws IOException { - if (assetInput != null) { - try { - assetInput.close(); - } catch (IOException e) { - // Ignore - } - } - try { - AssetFileDescriptor fd = InstrumentationRegistry.getInstrumentation() - .getContext().getAssets().openFd(assetFile); - assetFileSize = fd.getLength(); - fd.close(); - } catch (IOException e) { - // Can not determine file size - assetFileSize = -1; + public void run() { + isRunning = true; + while (isRunning) { + try { + if (!isPaused) { + // Serve static data only once !!! + serveStaticData(); + } else { + sleep(5); } + } catch (InterruptedException e) { + // Thread killed + } catch (IOException e) { + e.printStackTrace(); + Log.e(TAG, "Connection closed by the client !!!"); + isRunning = false; + } + } + } - assetInput = InstrumentationRegistry.getInstrumentation() - .getContext().getAssets().open(assetFile); - assetInput.mark(1000000000); - if (assetFileSize == -1) { - assetFileSize = assetInput.available(); - } + private void openAssetFile(String assetFile) throws IOException { + if (assetInput != null) { + try { + assetInput.close(); + } catch (IOException e) { + // Ignore + } + } + try { + AssetFileDescriptor fd = InstrumentationRegistry.getInstrumentation() + .getContext().getAssets().openFd(assetFile); + assetFileSize = fd.getLength(); + fd.close(); + } catch (IOException e) { + // Can not determine file size + assetFileSize = -1; } - /* - * Send HTTP response 416 Requested range not satisfiable - */ - private void sendRequestedRangeNotSatisfiable() throws IOException { - PrintWriter writer = new PrintWriter(new OutputStreamWriter( - httpOut, StandardCharsets.US_ASCII), true); - String response = "HTTP/1.1 416 Requested range not satisfiable\r\n" + - "Server: SimpleHttpServer/1.0\r\n" + - "Content-Range: bytes */" + assetFileSize + "\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: 0\r\n" + - "Connection: close\r\n" + - "\r\n"; - Log.i(TAG, "Sending response: \n" + response); - writer.write(response); - writer.flush(); + assetInput = InstrumentationRegistry.getInstrumentation() + .getContext().getAssets().open(assetFile); + assetInput.mark(1000000000); + if (assetFileSize == -1) { + assetFileSize = assetInput.available(); } + } - public void sendHTTPOKCompleteResponse(String contentType) throws IOException { - PrintWriter writer = new PrintWriter(new OutputStreamWriter( - httpOut, StandardCharsets.US_ASCII), true); - String response = "HTTP/1.1 200 OK\r\n" + - "Server: SimpleHttpServer/1.0\r\n" + - "Content-Type: " + contentType + "; charset=utf-8" + "\r\n" + - "Content-Length: " + assetFileSize + "\r\n" + - "Connection: keep-alive" + "\r\n" + - "Accept-Ranges: bytes" + "\r\n" + - (originHeaderValue.length() > 0 ? - ("Access-Control-Allow-Origin: " + originHeaderValue + "\r\n" + - "Access-Control-Allow-Credentials: true\r\n") : "") + - "\r\n"; - // Add asset file content - byte[] assetContent = new byte[(int)assetFileSize]; - int bytesRead = assetInput.read(assetContent); - String assetContentStr = new String(assetContent, StandardCharsets.UTF_8); - response = response + assetContentStr + "\r\n\r\n"; + /* + * Send HTTP response 416 Requested range not satisfiable + */ + private void sendRequestedRangeNotSatisfiable() throws IOException { + PrintWriter writer = new PrintWriter(new OutputStreamWriter( + httpOut, StandardCharsets.US_ASCII), true); + String response = "HTTP/1.1 416 Requested range not satisfiable\r\n" + + "Server: SimpleHttpServer/1.0\r\n" + + "Content-Range: bytes */" + assetFileSize + "\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Length: 0\r\n" + + "Connection: close\r\n" + + "\r\n"; + Log.i(TAG, "Sending response: \n" + response); + writer.write(response); + writer.flush(); + } - Log.w(TAG, "Sending response: \n" + response); - writer.write(response); - writer.flush(); - } + public void sendHTTPOKCompleteResponse(String contentType) throws IOException { + PrintWriter writer = new PrintWriter(new OutputStreamWriter( + httpOut, StandardCharsets.US_ASCII), true); + String response = "HTTP/1.1 200 OK\r\n" + + "Server: SimpleHttpServer/1.0\r\n" + + "Content-Type: " + contentType + "; charset=utf-8" + "\r\n" + + "Content-Length: " + assetFileSize + "\r\n" + + "Connection: keep-alive" + "\r\n" + + "Accept-Ranges: bytes" + "\r\n" + + (originHeaderValue.length() > 0 ? + ("Access-Control-Allow-Origin: " + originHeaderValue + "\r\n" + + "Access-Control-Allow-Credentials: true\r\n") : "") + + "\r\n"; + // Add asset file content + byte[] assetContent = new byte[(int) assetFileSize]; + int bytesRead = assetInput.read(assetContent); + String assetContentStr = new String(assetContent, StandardCharsets.UTF_8); + response = response + assetContentStr + "\r\n\r\n"; - /* - * Send HTTP response 206 partial content - */ - public void sendHTTPOKPartialResponse(String contentType, boolean acceptRangeHeader) - throws IOException { - PrintWriter writer = new PrintWriter(new OutputStreamWriter( - httpOut, StandardCharsets.US_ASCII), true); - String response = "HTTP/1.1 206 Partial Content\r\n" + - "Server: SimpleHttpServer/1.0\r\n" + - "Content-Type: " + contentType + "\r\n" + - ("Content-Range: bytes " + this.serveDataFromPosition + "-" + (assetFileSize-1) - + "/" + assetFileSize + "\r\n") + - (acceptRangeHeader ? ("Accept-Ranges: bytes" + "\r\n") : "") + - // content length should be total length - requested byte position - "Content-Length: " + (assetFileSize - this.serveDataFromPosition) + "\r\n" + - "Connection: close\r\n\r\n"; - Log.w(TAG, "Sending response: \n" + response); - writer.write(response); - writer.flush(); - } + Log.w(TAG, "Sending response: \n" + response); + writer.write(response); + writer.flush(); + } - /* - * Write limited amount of bytes to httpOut each 100 ms - */ - private void serveStaticData() throws IOException, InterruptedException { - // TODO see how to implement seek latency correctly + /* + * Send HTTP response 206 partial content + */ + public void sendHTTPOKPartialResponse(String contentType, boolean acceptRangeHeader) + throws IOException { + PrintWriter writer = new PrintWriter(new OutputStreamWriter( + httpOut, StandardCharsets.US_ASCII), true); + String response = "HTTP/1.1 206 Partial Content\r\n" + + "Server: SimpleHttpServer/1.0\r\n" + + "Content-Type: " + contentType + "\r\n" + + ("Content-Range: bytes " + this.serveDataFromPosition + "-" + (assetFileSize - 1) + + "/" + assetFileSize + "\r\n") + + (acceptRangeHeader ? ("Accept-Ranges: bytes" + "\r\n") : "") + + // content length should be total length - requested byte position + "Content-Length: " + (assetFileSize - this.serveDataFromPosition) + "\r\n" + + "Connection: close\r\n\r\n"; + Log.w(TAG, "Sending response: \n" + response); + writer.write(response); + writer.flush(); + } + + /* + * Write limited amount of bytes to httpOut each 100 ms + */ + private void serveStaticData() throws IOException, InterruptedException { + // TODO see how to implement seek latency correctly // if (!seekLatencyServed && serveDataFromPosition > 1000 && // serveDataFromPosition < ((assetFileSize / 10) * 7)) { // Log.e("MuxStats", "Sleeping for: " + seekLatency); // Thread.sleep(seekLatency); // seekLatencyServed = true; // } - int bytesToRead = transferBufferSize; - if (networkJammingEndPeriod > System.currentTimeMillis()) { - int jamFactor = this.networkJamFactor; - if (!constantJam) { - jamFactor = r.nextInt(this.networkJamFactor) + 2; - } - bytesToRead = (int)((double)bytesToRead / (double)jamFactor); - } + int bytesToRead = transferBufferSize; + if (networkJammingEndPeriod > System.currentTimeMillis()) { + int jamFactor = this.networkJamFactor; + if (!constantJam) { + jamFactor = r.nextInt(this.networkJamFactor) + 2; + } + bytesToRead = (int) ((double) bytesToRead / (double) jamFactor); + } - int bytesRead = assetInput.read(transferBuffer, 0, bytesToRead); - if (bytesRead == -1) { - // EOF reached - Log.e(TAG, "EOF reached !!!"); - isRunning = false; - return; - } - if (bytesRead > 0) { - httpOut.write(transferBuffer, 0, bytesRead); - sleep(10); - } + int bytesRead = assetInput.read(transferBuffer, 0, bytesToRead); + if (bytesRead == -1) { + // EOF reached + Log.e(TAG, "EOF reached !!!"); + isRunning = false; + return; + } + if (bytesRead > 0) { + httpOut.write(transferBuffer, 0, bytesRead); + sleep(10); } + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionWorker.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionWorker.java index 9e298962..337b37f1 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionWorker.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ConnectionWorker.java @@ -6,67 +6,67 @@ public class ConnectionWorker extends Thread { - Socket clientSocket; - int bandwidthLimit; - boolean isRunning; - ConnectionReceiver receiver; - ConnectionSender sender; - private long networkJamEndPeriod = -1; - private int networkJamFactor = 1; - private boolean constantJam = false; - int seekLatency; + Socket clientSocket; + int bandwidthLimit; + boolean isRunning; + ConnectionReceiver receiver; + ConnectionSender sender; + private long networkJamEndPeriod = -1; + private int networkJamFactor = 1; + private boolean constantJam = false; + int seekLatency; - public ConnectionWorker(Socket clientSocket, int bandwidthLimit, - long networkJamEndPeriod, int networkJamFactor, - boolean constantJam, int seekLatency) { - this.clientSocket = clientSocket; - this.bandwidthLimit = bandwidthLimit; - this.constantJam = constantJam; - this.networkJamEndPeriod = networkJamEndPeriod; - this.networkJamFactor = networkJamFactor; - this.seekLatency = seekLatency; - start(); - } + public ConnectionWorker(Socket clientSocket, int bandwidthLimit, + long networkJamEndPeriod, int networkJamFactor, + boolean constantJam, int seekLatency) { + this.clientSocket = clientSocket; + this.bandwidthLimit = bandwidthLimit; + this.constantJam = constantJam; + this.networkJamEndPeriod = networkJamEndPeriod; + this.networkJamFactor = networkJamFactor; + this.seekLatency = seekLatency; + start(); + } - public void jamNetwork(long jamPeriod, int jamFactor, boolean constantJam) { - sender.jamNetwork(jamPeriod, jamFactor, constantJam); - } + public void jamNetwork(long jamPeriod, int jamFactor, boolean constantJam) { + sender.jamNetwork(jamPeriod, jamFactor, constantJam); + } - public void run() { - isRunning = true; - try { - receiver = new ConnectionReceiver(clientSocket.getInputStream()); - receiver.start(); - sender = new ConnectionSender(clientSocket.getOutputStream(), bandwidthLimit, - networkJamEndPeriod, networkJamFactor, seekLatency); - sender.pause(); - } catch (IOException e) { - e.printStackTrace(); - isRunning = false; - } - while (isRunning) { - try { - ServerAction action = receiver.getNextAction(); - sender.pause(); - if (action.getType() == ServerAction.SERVE_MEDIA_DATA) { - sender.startServingFromPosition(action.getAssetFile(), action.getHeaders()); - } - } catch (IOException e) { - isRunning = false; - } catch (InterruptedException e) { - // Someone killed the thread - } + public void run() { + isRunning = true; + try { + receiver = new ConnectionReceiver(clientSocket.getInputStream()); + receiver.start(); + sender = new ConnectionSender(clientSocket.getOutputStream(), bandwidthLimit, + networkJamEndPeriod, networkJamFactor, seekLatency); + sender.pause(); + } catch (IOException e) { + e.printStackTrace(); + isRunning = false; + } + while (isRunning) { + try { + ServerAction action = receiver.getNextAction(); + sender.pause(); + if (action.getType() == ServerAction.SERVE_MEDIA_DATA) { + sender.startServingFromPosition(action.getAssetFile(), action.getHeaders()); } + } catch (IOException e) { + isRunning = false; + } catch (InterruptedException e) { + // Someone killed the thread + } } + } - public void kill() { - isRunning = false; - interrupt(); - if (sender != null) { - sender.kill(); - } - if (receiver != null) { - receiver.kill(); - } + public void kill() { + isRunning = false; + interrupt(); + if (sender != null) { + sender.kill(); + } + if (receiver != null) { + receiver.kill(); } + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/HttpFormatException.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/HttpFormatException.java index df0b4f27..16b6c893 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/HttpFormatException.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/HttpFormatException.java @@ -2,7 +2,7 @@ public class HttpFormatException extends Exception { - public HttpFormatException(String message) { - super(message); - } + public HttpFormatException(String message) { + super(message); + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/HttpRequestParser.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/HttpRequestParser.java index 47e0c054..4ad853bd 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/HttpRequestParser.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/HttpRequestParser.java @@ -6,94 +6,91 @@ import java.util.Hashtable; public class HttpRequestParser { - private String _requestLine; - private Hashtable _requestHeaders; - private StringBuffer _messagetBody; - public HttpRequestParser() { - _requestHeaders = new Hashtable(); - _messagetBody = new StringBuffer(); - } - - /** - * Parse and HTTP request. - * - * @param request - * String holding http request. - * @throws IOException - * If an I/O error occurs reading the input stream. - * @throws HttpFormatException - * If HTTP Request is malformed - */ - public void parseRequest(String request) throws IOException, HttpFormatException { - BufferedReader reader = new BufferedReader(new StringReader(request)); + private String _requestLine; + private final Hashtable _requestHeaders; + private final StringBuffer _messagetBody; - setRequestLine(reader.readLine()); // Request-Line ; Section 5.1 + public HttpRequestParser() { + _requestHeaders = new Hashtable(); + _messagetBody = new StringBuffer(); + } - String header = reader.readLine(); - while (header.length() > 0) { - appendHeaderParameter(header); - header = reader.readLine(); - } + /** + * Parse and HTTP request. + * + * @param request String holding http request. + * @throws IOException If an I/O error occurs reading the input stream. + * @throws HttpFormatException If HTTP Request is malformed + */ + public void parseRequest(String request) throws IOException, HttpFormatException { + BufferedReader reader = new BufferedReader(new StringReader(request)); - String bodyLine = reader.readLine(); - while (bodyLine != null) { - appendMessageBody(bodyLine); - bodyLine = reader.readLine(); - } + setRequestLine(reader.readLine()); // Request-Line ; Section 5.1 + String header = reader.readLine(); + while (header.length() > 0) { + appendHeaderParameter(header); + header = reader.readLine(); } - /** - * - * 5.1 Request-Line The Request-Line begins with a method token, followed by - * the Request-URI and the protocol version, and ending with CRLF. The - * elements are separated by SP characters. No CR or LF is allowed except in - * the final CRLF sequence. - * - * @return String with Request-Line - */ - public String getRequestLine() { - return _requestLine; + String bodyLine = reader.readLine(); + while (bodyLine != null) { + appendMessageBody(bodyLine); + bodyLine = reader.readLine(); } - private void setRequestLine(String requestLine) throws HttpFormatException { - if (requestLine == null || requestLine.length() == 0) { - throw new HttpFormatException("Invalid Request-Line: " + requestLine); - } - _requestLine = requestLine; - } + } - private void appendHeaderParameter(String header) throws HttpFormatException { - int idx = header.indexOf(":"); - if (idx == -1) { - throw new HttpFormatException("Invalid Header Parameter: " + header); - } - _requestHeaders.put(header.substring(0, idx), header.substring(idx + 1, header.length())); - } + /** + * 5.1 Request-Line The Request-Line begins with a method token, followed by the Request-URI and + * the protocol version, and ending with CRLF. The elements are separated by SP characters. No CR + * or LF is allowed except in the final CRLF sequence. + * + * @return String with Request-Line + */ + public String getRequestLine() { + return _requestLine; + } - /** - * The message-body (if any) of an HTTP message is used to carry the - * entity-body associated with the request or response. The message-body - * differs from the entity-body only when a transfer-coding has been - * applied, as indicated by the Transfer-Encoding header field (section - * 14.41). - * @return String with message-body - */ - public String getMessageBody() { - return _messagetBody.toString(); + private void setRequestLine(String requestLine) throws HttpFormatException { + if (requestLine == null || requestLine.length() == 0) { + throw new HttpFormatException("Invalid Request-Line: " + requestLine); } + _requestLine = requestLine; + } - private void appendMessageBody(String bodyLine) { - _messagetBody.append(bodyLine).append("\r\n"); + private void appendHeaderParameter(String header) throws HttpFormatException { + int idx = header.indexOf(":"); + if (idx == -1) { + throw new HttpFormatException("Invalid Header Parameter: " + header); } + _requestHeaders.put(header.substring(0, idx), header.substring(idx + 1)); + } - /** - * For list of available headers refer to sections: 4.5, 5.3, 7.1 of RFC 2616 - * @param headerName Name of header - * @return String with the value of the header or null if not found. - */ - public String getHeaderParam(String headerName){ - return _requestHeaders.get(headerName); - } + /** + * The message-body (if any) of an HTTP message is used to carry the entity-body associated with + * the request or response. The message-body differs from the entity-body only when a + * transfer-coding has been applied, as indicated by the Transfer-Encoding header field (section + * 14.41). + * + * @return String with message-body + */ + public String getMessageBody() { + return _messagetBody.toString(); + } + + private void appendMessageBody(String bodyLine) { + _messagetBody.append(bodyLine).append("\r\n"); + } + + /** + * For list of available headers refer to sections: 4.5, 5.3, 7.1 of RFC 2616 + * + * @param headerName Name of header + * @return String with the value of the header or null if not found. + */ + public String getHeaderParam(String headerName) { + return _requestHeaders.get(headerName); + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ServerAction.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ServerAction.java index fc7b92de..c3b09a26 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ServerAction.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/ServerAction.java @@ -1,35 +1,34 @@ package com.mux.stats.sdk.muxstats.automatedtests.mockup.http; -import java.util.ArrayList; import java.util.HashMap; public class ServerAction { - public static final int SERVE_MEDIA_DATA = 0; + public static final int SERVE_MEDIA_DATA = 0; - int type; - HashMap headers; - String assetFile; + int type; + HashMap headers; + String assetFile; - public ServerAction(int type) { - this.type = type; - } + public ServerAction(int type) { + this.type = type; + } - public ServerAction(int type, HashMap headers, String assetFile) { - this.type = type; - this.headers = headers; - this.assetFile = assetFile; - } + public ServerAction(int type, HashMap headers, String assetFile) { + this.type = type; + this.headers = headers; + this.assetFile = assetFile; + } - public int getType() { - return type; - } + public int getType() { + return type; + } - public HashMap getHeaders() { - return headers; - }; + public HashMap getHeaders() { + return headers; + } - public String getAssetFile() { - return assetFile; - } + public String getAssetFile() { + return assetFile; + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/SimpleHTTPServer.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/SimpleHTTPServer.java index 0f0bb3f5..554b4001 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/SimpleHTTPServer.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/mockup/http/SimpleHTTPServer.java @@ -6,96 +6,96 @@ import java.net.Socket; import java.util.ArrayList; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.locks.Condition; public class SimpleHTTPServer extends Thread { - private boolean isRunning; - private int port; - private int bandwidthLimit; - private long networkJamEndPeriod = -1; - private int networkJamFactor = 1; - private int seekLatency; - private boolean constantJam = false; + private boolean isRunning; + private final int port; + private final int bandwidthLimit; + private long networkJamEndPeriod = -1; + private int networkJamFactor = 1; + private int seekLatency; + private boolean constantJam = false; - private ServerSocket server; - ArrayList workers = new ArrayList<>(); + private final ServerSocket server; + ArrayList workers = new ArrayList<>(); - Lock serverLock = new ReentrantLock(); - Condition serverDied = serverLock.newCondition(); + Lock serverLock = new ReentrantLock(); + Condition serverDied = serverLock.newCondition(); - /* - * Run a server on localhost:port - */ - public SimpleHTTPServer(int port, int bandwidthLimit) throws IOException { - this.port = port; - this.bandwidthLimit = bandwidthLimit; - server = new ServerSocket(port); - seekLatency = 0; - start(); - } + /* + * Run a server on localhost:port + */ + public SimpleHTTPServer(int port, int bandwidthLimit) throws IOException { + this.port = port; + this.bandwidthLimit = bandwidthLimit; + server = new ServerSocket(port); + seekLatency = 0; + start(); + } - /* - * This is the number of MS that will be waited before serving data for requested range - */ - public void setSeekLatency(int latency) { - seekLatency = latency; - } + /* + * This is the number of MS that will be waited before serving data for requested range + */ + public void setSeekLatency(int latency) { + seekLatency = latency; + } - public void jamNetwork(long jamPeriod, int jamFactor, boolean constantJam) { - this.networkJamEndPeriod = System.currentTimeMillis() + jamPeriod; - this.networkJamFactor = jamFactor; - this.constantJam = constantJam; - for(ConnectionWorker worker : workers) { - worker.jamNetwork(jamPeriod, jamFactor, constantJam); - } + public void jamNetwork(long jamPeriod, int jamFactor, boolean constantJam) { + this.networkJamEndPeriod = System.currentTimeMillis() + jamPeriod; + this.networkJamFactor = jamFactor; + this.constantJam = constantJam; + for (ConnectionWorker worker : workers) { + worker.jamNetwork(jamPeriod, jamFactor, constantJam); } + } - public void run() { - isRunning = true; - while (isRunning) { - try { - acceptConnection(); + public void run() { + isRunning = true; + while (isRunning) { + try { + acceptConnection(); // } catch (InterruptedException e) { // // Ignore, sombody killed the server - } catch (IOException e) { - // TODO handle this - e.printStackTrace(); - } - } - for (ConnectionWorker worker : workers) { - worker.kill(); - } - serverLock.lock(); - serverDied.signalAll(); - serverLock.unlock(); + } catch (IOException e) { + // TODO handle this + e.printStackTrace(); + } } - - public void kill() { - isRunning = false; - try { - server.close(); - } catch (IOException e) { - e.printStackTrace(); - } - // Wait for server to die - try { - serverLock.lock(); - serverDied.await(10000, TimeUnit.MILLISECONDS); - serverLock.unlock(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + for (ConnectionWorker worker : workers) { + worker.kill(); } + serverLock.lock(); + serverDied.signalAll(); + serverLock.unlock(); + } - /* - * Process HTTP connections - */ - private void acceptConnection() throws IOException { - Socket clientSocket = server.accept(); - workers.add(new ConnectionWorker(clientSocket, bandwidthLimit, - networkJamEndPeriod, networkJamFactor, constantJam, seekLatency)); + public void kill() { + isRunning = false; + try { + server.close(); + } catch (IOException e) { + e.printStackTrace(); + } + // Wait for server to die + try { + serverLock.lock(); + serverDied.await(10000, TimeUnit.MILLISECONDS); + serverLock.unlock(); + } catch (InterruptedException e) { + e.printStackTrace(); } + } + + /* + * Process HTTP connections + */ + private void acceptConnection() throws IOException { + Socket clientSocket = server.accept(); + workers.add(new ConnectionWorker(clientSocket, bandwidthLimit, + networkJamEndPeriod, networkJamFactor, constantJam, seekLatency)); + } } diff --git a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerBaseActivity.java b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerBaseActivity.java index 0e1d30d1..d664d735 100644 --- a/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerBaseActivity.java +++ b/automatedtests/src/androidTest/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerBaseActivity.java @@ -1,6 +1,5 @@ package com.mux.stats.sdk.muxstats.automatedtests.ui; -import android.app.AlertDialog; import android.app.PendingIntent; import android.content.Intent; import android.graphics.Bitmap; @@ -11,18 +10,12 @@ import android.os.Bundle; import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; -import android.util.Pair; -import android.view.View; import android.view.WindowManager; -import android.widget.Button; -import android.widget.LinearLayout; - import androidx.annotation.MainThread; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; - import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackPreparer; @@ -39,12 +32,10 @@ import com.google.android.exoplayer2.ui.PlayerView; import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; -import com.mux.stats.sdk.muxstats.MuxStats; -import com.mux.stats.sdk.muxstats.automatedtests.BuildConfig; import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; +import com.mux.stats.sdk.muxstats.automatedtests.BuildConfig; import com.mux.stats.sdk.muxstats.automatedtests.R; import com.mux.stats.sdk.muxstats.automatedtests.mockup.MockNetworkRequest; - import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; @@ -52,360 +43,362 @@ import java.util.concurrent.locks.ReentrantLock; public abstract class SimplePlayerBaseActivity extends AppCompatActivity implements - PlaybackPreparer, Player.EventListener { - - static final String TAG = "SimplePlayerActivity"; - - protected static final String PLAYBACK_CHANNEL_ID = "playback_channel"; - protected static final int PLAYBACK_NOTIFICATION_ID = 1; - protected static final String ARG_URI = "uri_string"; - protected static final String ARG_TITLE = "title"; - protected static final String ARG_START_POSITION = "start_position"; - - String videoTitle = "Test Video"; - String urlToPlay; - PlayerView playerView; - SimpleExoPlayer player; - DefaultTrackSelector trackSelector; - MediaSource testMediaSource; - MuxStatsExoPlayer muxStats; - AdsLoader adsLoader; - Uri loadedAdTagUri; - MockNetworkRequest mockNetwork; - AtomicBoolean onResumedCalled = new AtomicBoolean(false); - PlayerNotificationManager notificationManager; - MediaSessionCompat mediaSessionCompat; - MediaSessionConnector mediaSessionConnector; - - Lock activityLock = new ReentrantLock(); - Condition playbackEnded = activityLock.newCondition(); - Condition playbackStarted = activityLock.newCondition(); - Condition playbackBuffering = activityLock.newCondition(); - Condition activityClosed = activityLock.newCondition(); - Condition activityInitialized = activityLock.newCondition(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_simple_player_test); - disableUserActions(); - - playerView = findViewById(R.id.player_view); - playerView.setPlaybackPreparer(this); - - initExoPlayer(); - player.addListener(this); - playerView.setPlayer(player); - - // Do not hide controlls - playerView.setControllerShowTimeoutMs(0); - playerView.setControllerHideOnTouch(false); - - // Setup notification and media session. - initAudioSession(); - } - - @Override - protected void onResume() { - super.onResume(); - onResumedCalled.set(true); - } - - @Override - public void onStop() { - super.onStop(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - signalActivityClosed(); - if ( muxStats != null ) { - muxStats.release(); - } - } - - public abstract void initExoPlayer(); - - public abstract void initAudioSession(); - - public abstract void startPlayback(); - - public void setVideoTitle(String title) { - videoTitle = title; - } - - public void setAdTag(String tag) { - loadedAdTagUri = Uri.parse(tag); - } - - public void setUrlToPlay(String url) { - urlToPlay = url; - } - - public DefaultTrackSelector getTrackSelector() { - return trackSelector; - } - - - - public void releaseExoPlayer() { - player.release(); - player = null; - } - - public MuxStatsExoPlayer getMuxStats() { - return muxStats; - } - - public void initMuxSats() { - // Mux details - CustomerPlayerData customerPlayerData = new CustomerPlayerData(); - if (BuildConfig.SHOULD_REPORT_INSTRUMENTATION_TEST_EVENTS_TO_SERVER) { - customerPlayerData.setEnvironmentKey(BuildConfig.INSTRUMENTATION_TEST_ENVIRONMENT_KEY); - } else { - customerPlayerData.setEnvironmentKey("YOUR_ENVIRONMENT_KEY"); - } - CustomerVideoData customerVideoData = new CustomerVideoData(); - customerVideoData.setVideoTitle(videoTitle); - mockNetwork = new MockNetworkRequest(); - muxStats = new MuxStatsExoPlayer( - this, player, "demo-player", customerPlayerData, customerVideoData, - null,true, mockNetwork); - Point size = new Point(); - getWindowManager().getDefaultDisplay().getSize(size); - muxStats.setScreenSize(size.x, size.y); - muxStats.setPlayerView(playerView); - muxStats.enableMuxCoreDebug(true, false); - } - - public MediaSource getTestMediaSource() { - return testMediaSource; - } - - public PlayerView getPlayerView() { - return playerView; - } - - public MockNetworkRequest getMockNetwork() { - return mockNetwork; - } - - public void waitForPlaybackToFinish() { - try { - activityLock.lock(); - playbackEnded.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - activityLock.unlock(); - } - } - - public void waitForActivityToInitialize() { - if (!onResumedCalled.get()) { - try { - activityLock.lock(); - activityInitialized.await(); - activityLock.unlock(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - public boolean waitForPlaybackToStart(long timeoutInMs) { - try { - activityLock.lock(); - return playbackStarted.await(timeoutInMs, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - return false; - } finally { - activityLock.unlock(); - } - } - - public void waitForPlaybackToStartBuffering() { - if (player.getPlaybackState() == Player.STATE_READY && - player.getPlayWhenReady()) { - try { - activityLock.lock(); - playbackBuffering.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - activityLock.unlock(); - } - } - } - - public void waitForActivityToClose() { - try { - activityLock.lock(); - activityClosed.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - activityLock.unlock(); - } - } - - public void signalPlaybackStarted() { + PlaybackPreparer, Player.EventListener { + + static final String TAG = "SimplePlayerActivity"; + + protected static final String PLAYBACK_CHANNEL_ID = "playback_channel"; + protected static final int PLAYBACK_NOTIFICATION_ID = 1; + protected static final String ARG_URI = "uri_string"; + protected static final String ARG_TITLE = "title"; + protected static final String ARG_START_POSITION = "start_position"; + + String videoTitle = "Test Video"; + String urlToPlay; + PlayerView playerView; + SimpleExoPlayer player; + DefaultTrackSelector trackSelector; + MediaSource testMediaSource; + MuxStatsExoPlayer muxStats; + AdsLoader adsLoader; + Uri loadedAdTagUri; + MockNetworkRequest mockNetwork; + AtomicBoolean onResumedCalled = new AtomicBoolean(false); + PlayerNotificationManager notificationManager; + MediaSessionCompat mediaSessionCompat; + MediaSessionConnector mediaSessionConnector; + + Lock activityLock = new ReentrantLock(); + Condition playbackEnded = activityLock.newCondition(); + Condition playbackStarted = activityLock.newCondition(); + Condition playbackBuffering = activityLock.newCondition(); + Condition activityClosed = activityLock.newCondition(); + Condition activityInitialized = activityLock.newCondition(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_simple_player_test); + disableUserActions(); + + playerView = findViewById(R.id.player_view); + playerView.setPlaybackPreparer(this); + + initExoPlayer(); + player.addListener(this); + playerView.setPlayer(player); + + // Do not hide controlls + playerView.setControllerShowTimeoutMs(0); + playerView.setControllerHideOnTouch(false); + + // Setup notification and media session. + initAudioSession(); + } + + @Override + protected void onResume() { + super.onResume(); + onResumedCalled.set(true); + } + + @Override + public void onStop() { + super.onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + signalActivityClosed(); + if (muxStats != null) { + muxStats.release(); + } + } + + public abstract void initExoPlayer(); + + public abstract void initAudioSession(); + + public abstract void startPlayback(); + + public void setVideoTitle(String title) { + videoTitle = title; + } + + public void setAdTag(String tag) { + loadedAdTagUri = Uri.parse(tag); + } + + public void setUrlToPlay(String url) { + urlToPlay = url; + } + + public DefaultTrackSelector getTrackSelector() { + return trackSelector; + } + + + public void releaseExoPlayer() { + player.release(); + player = null; + } + + public MuxStatsExoPlayer getMuxStats() { + return muxStats; + } + + public void initMuxSats() { + // Mux details + CustomerPlayerData customerPlayerData = new CustomerPlayerData(); + if (BuildConfig.SHOULD_REPORT_INSTRUMENTATION_TEST_EVENTS_TO_SERVER) { + customerPlayerData.setEnvironmentKey(BuildConfig.INSTRUMENTATION_TEST_ENVIRONMENT_KEY); + } else { + customerPlayerData.setEnvironmentKey("YOUR_ENVIRONMENT_KEY"); + } + CustomerVideoData customerVideoData = new CustomerVideoData(); + customerVideoData.setVideoTitle(videoTitle); + mockNetwork = new MockNetworkRequest(); + muxStats = new MuxStatsExoPlayer( + this, player, "demo-player", customerPlayerData, customerVideoData, + null, true, mockNetwork); + Point size = new Point(); + getWindowManager().getDefaultDisplay().getSize(size); + muxStats.setScreenSize(size.x, size.y); + muxStats.setPlayerView(playerView); + muxStats.enableMuxCoreDebug(true, false); + } + + public MediaSource getTestMediaSource() { + return testMediaSource; + } + + public PlayerView getPlayerView() { + return playerView; + } + + public MockNetworkRequest getMockNetwork() { + return mockNetwork; + } + + public boolean waitForPlaybackToFinish(long timeoutInMs) { + try { + activityLock.lock(); + playbackEnded.await(timeoutInMs, TimeUnit.MILLISECONDS); + return true; + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } finally { + activityLock.unlock(); + } + } + + public void waitForActivityToInitialize() { + if (!onResumedCalled.get()) { + try { activityLock.lock(); - playbackStarted.signalAll(); + activityInitialized.await(); activityLock.unlock(); - } - - public void signalPlaybackBuffering() { + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public boolean waitForPlaybackToStart(long timeoutInMs) { + try { + activityLock.lock(); + return playbackStarted.await(timeoutInMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } finally { + activityLock.unlock(); + } + } + + public void waitForPlaybackToStartBuffering() { + if (player.getPlaybackState() == Player.STATE_READY && + player.getPlayWhenReady()) { + try { activityLock.lock(); - playbackBuffering.signalAll(); + playbackBuffering.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { activityLock.unlock(); - } + } + } + } + + public void waitForActivityToClose() { + try { + activityLock.lock(); + activityClosed.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + activityLock.unlock(); + } + } + + public void signalPlaybackStarted() { + activityLock.lock(); + playbackStarted.signalAll(); + activityLock.unlock(); + } + + public void signalPlaybackBuffering() { + activityLock.lock(); + playbackBuffering.signalAll(); + activityLock.unlock(); + } + + public void signalPlaybackEnded() { + activityLock.lock(); + playbackEnded.signalAll(); + activityLock.unlock(); + } + + public void signalActivityClosed() { + activityLock.lock(); + activityClosed.signalAll(); + activityLock.unlock(); + } + + private void disableUserActions() { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); + } + + private void enableUserActions() { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); + } + + /////////////////////////////////////////////////////////////////////// + ///////// PlaybackPreparer //////////////////////////////////////////// + + @Override + public void preparePlayback() { +// player.retry(); + } - public void signalPlaybackEnded() { - activityLock.lock(); - playbackEnded.signalAll(); - activityLock.unlock(); - } + ////////////////////////////////////////////////////////////////////// + ////// Player.EventListener ////////////////////////////////////////// - public void signalActivityClosed() { - activityLock.lock(); - activityClosed.signalAll(); - activityLock.unlock(); - } + @Override + public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { - private void disableUserActions() { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); - } + } - private void enableUserActions() { - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); - } + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - /////////////////////////////////////////////////////////////////////// - ///////// PlaybackPreparer //////////////////////////////////////////// + } - @Override - public void preparePlayback() { -// player.retry(); + @Override + public void onLoadingChanged(boolean isLoading) { + + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + switch (playbackState) { + case Player.STATE_BUFFERING: + signalPlaybackBuffering(); + break; + case Player.STATE_ENDED: + signalPlaybackEnded(); + break; + case Player.STATE_READY: + // By the time we get here, it depends on playWhenReady to know if we're playing + if (playWhenReady) { + signalPlaybackStarted(); + } else { + // TODO implement this +// signalPlaybackPaused(); + } + break; } + } - ////////////////////////////////////////////////////////////////////// - ////// Player.EventListener ////////////////////////////////////////// + @Override + public void onRepeatModeChanged(int repeatMode) { + activityLock.lock(); + activityInitialized.signalAll(); + activityLock.unlock(); + } - @Override - public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { + @Override + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { - } + } - @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + @Override + public void onPlayerError(ExoPlaybackException error) { + Log.e(TAG, error.getMessage()); + error.printStackTrace(); + } - } + @Override + public void onPositionDiscontinuity(int reason) { - @Override - public void onLoadingChanged(boolean isLoading) { + } - } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - switch (playbackState) { - case Player.STATE_BUFFERING: - signalPlaybackBuffering(); - break; - case Player.STATE_ENDED: - signalPlaybackEnded(); - break; - case Player.STATE_READY: - // By the time we get here, it depends on playWhenReady to know if we're playing - if (playWhenReady) { - signalPlaybackStarted(); - } else { - // TODO implement this -// signalPlaybackPaused(); - } - break; - } - } + } - @Override - public void onRepeatModeChanged(int repeatMode) { - activityLock.lock(); - activityInitialized.signalAll(); - activityLock.unlock(); - } + @Override + public void onSeekProcessed() { - @Override - public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { + } - } + class MDAdapter implements PlayerNotificationManager.MediaDescriptionAdapter { @Override - public void onPlayerError(ExoPlaybackException error) { - Log.e(TAG, error.getMessage()); - error.printStackTrace(); + public String getCurrentContentTitle(Player player) { + return "Tittle"; } + @Nullable @Override - public void onPositionDiscontinuity(int reason) { - + public PendingIntent createCurrentContentIntent(Player player) { + return PendingIntent.getActivity( + getApplicationContext(), + 0, + new Intent(getApplicationContext(), SimplePlayerBaseActivity.class), + PendingIntent.FLAG_UPDATE_CURRENT + ); } + @Nullable @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - + public String getCurrentContentText(Player player) { + return "Automated test playback"; } + @Nullable @Override - public void onSeekProcessed() { - - } - - class MDAdapter implements PlayerNotificationManager.MediaDescriptionAdapter { - - @Override - public String getCurrentContentTitle(Player player) { - return "Tittle"; - } - - @Nullable - @Override - public PendingIntent createCurrentContentIntent(Player player) { - return PendingIntent.getActivity( - getApplicationContext(), - 0, - new Intent(getApplicationContext(), SimplePlayerBaseActivity.class), - PendingIntent.FLAG_UPDATE_CURRENT - ); - } - - @Nullable - @Override - public String getCurrentContentText(Player player) { - return "Automated test playback"; - } - - @Nullable - @Override - public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) { - return getBitmapFromVectorDrawable(R.drawable.ic_launcher_foreground); - } - - @MainThread - private Bitmap getBitmapFromVectorDrawable(int drawableId) { - Drawable drawable = ContextCompat.getDrawable(getApplicationContext(), drawableId); - DrawableCompat.wrap(drawable).mutate(); - Bitmap bmp = Bitmap.createBitmap( - drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight(), - Bitmap.Config.ARGB_8888); - Canvas cnvs = new Canvas(bmp); - drawable.setBounds(0, 0, cnvs.getWidth(), cnvs.getHeight()); - drawable.draw(cnvs); - return bmp; - } - } -} + public Bitmap getCurrentLargeIcon(Player player, + PlayerNotificationManager.BitmapCallback callback) { + return getBitmapFromVectorDrawable(R.drawable.ic_launcher_foreground); + } + + @MainThread + private Bitmap getBitmapFromVectorDrawable(int drawableId) { + Drawable drawable = ContextCompat.getDrawable(getApplicationContext(), drawableId); + DrawableCompat.wrap(drawable).mutate(); + Bitmap bmp = Bitmap.createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + Canvas cnvs = new Canvas(bmp); + drawable.setBounds(0, 0, cnvs.getWidth(), cnvs.getHeight()); + drawable.draw(cnvs); + return bmp; + } + } +} \ No newline at end of file diff --git a/automatedtests/src/androidTestR2_10_6/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/automatedtests/src/androidTestR2_10_6/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 335f8374..e2b463c8 100644 --- a/automatedtests/src/androidTestR2_10_6/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/automatedtests/src/androidTestR2_10_6/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -19,11 +19,11 @@ import android.net.Uri; import android.os.Looper; import android.os.SystemClock; +import android.view.View; +import android.view.ViewGroup; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import android.view.View; -import android.view.ViewGroup; import com.google.ads.interactivemedia.v3.api.Ad; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; import com.google.ads.interactivemedia.v3.api.AdError; @@ -81,31 +81,36 @@ * #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling * {@link #release()}. * - *

The IMA SDK can take into account video control overlay views when calculating ad viewability. - * For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} and {@link - * AdViewProvider#getAdOverlayViews()}. + *

The IMA SDK can take into account video control overlay views when calculating ad + * viewability. For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} + * and {@link AdViewProvider#getAdOverlayViews()}. */ public final class ImaAdsLoader implements Player.EventListener, - AdsLoader, - VideoAdPlayer, - ContentProgressProvider, - AdErrorListener, - AdsLoadedListener, - AdEventListener { + AdsLoader, + VideoAdPlayer, + ContentProgressProvider, + AdErrorListener, + AdsLoadedListener, + AdEventListener { static { ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); } - /** Builder for {@link ImaAdsLoader}. */ + /** + * Builder for {@link ImaAdsLoader}. + */ public static final class Builder { private final Context context; - @Nullable private ImaSdkSettings imaSdkSettings; - @Nullable private AdEventListener adEventListener; - @Nullable private Set adUiElements; + @Nullable + private ImaSdkSettings imaSdkSettings; + @Nullable + private AdEventListener adEventListener; + @Nullable + private Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; private int mediaBitrate; @@ -141,8 +146,7 @@ public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { } /** - * Sets a listener for ad events that will be passed to {@link - * AdsManager#addAdEventListener(AdEventListener)}. + * Sets a listener for ad events that will be passed to {@link AdsManager#addAdEventListener(AdEventListener)}. * * @param adEventListener The ad event listener. * @return This builder, for convenience. @@ -208,7 +212,7 @@ public Builder setMaxMediaBitrate(int bitrate) { * setting is {@code true}. * * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on - * Android TV devices. + * Android TV devices. * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ @@ -218,7 +222,7 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila } @VisibleForTesting - /* package */ Builder setImaFactory(ImaFactory imaFactory) { + /* package */ Builder setImaFactory(ImaFactory imaFactory) { this.imaFactory = Assertions.checkNotNull(imaFactory); return this; } @@ -226,9 +230,8 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila /** * Returns a new {@link ImaAdsLoader} for the specified ad tag. * - * @param adTagUri The URI of a compatible ad tag to load. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * information on compatible ad tags. + * @param adTagUri The URI of a compatible ad tag to load. See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for information on compatible ad tags. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdTag(Uri adTagUri) { @@ -250,7 +253,7 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) { * Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response. * * @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of - * making a request via an ad tag URL. + * making a request via an ad tag URL. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdsResponse(String adsResponse) { @@ -280,7 +283,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private static final String IMA_SDK_SETTINGS_PLAYER_TYPE = "google/exo.ext.ima"; private static final String IMA_SDK_SETTINGS_PLAYER_VERSION = ExoPlayerLibraryInfo.VERSION; - /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ + /** + * The value used in {@link VideoProgressUpdate}s to indicate an unset duration. + */ private static final long IMA_DURATION_UNSET = -1L; /** @@ -289,17 +294,24 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final long END_OF_CONTENT_POSITION_THRESHOLD_MS = 5000; - /** The maximum duration before an ad break that IMA may start preloading the next ad. */ + /** + * The maximum duration before an ad break that IMA may start preloading the next ad. + */ private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000; private static final int TIMEOUT_UNSET = -1; private static final int BITRATE_UNSET = -1; - /** The state of ad playback. */ + /** + * The state of ad playback. + */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) - private @interface ImaAdState {} + private @interface ImaAdState { + + } + /** * The ad playback state when IMA is not playing an ad. */ @@ -313,14 +325,18 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final int IMA_AD_STATE_PAUSED = 2; - private final @Nullable Uri adTagUri; - private final @Nullable String adsResponse; + private final @Nullable + Uri adTagUri; + private final @Nullable + String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; private final boolean focusSkipButtonWhenAvailable; private final int mediaBitrate; - private final @Nullable Set adUiElements; - private final @Nullable AdEventListener adEventListener; + private final @Nullable + Set adUiElements; + private final @Nullable + AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; private final List adCallbacks; @@ -328,11 +344,14 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader; private boolean wasSetPlayerCalled; - @Nullable private Player nextPlayer; + @Nullable + private Player nextPlayer; private Object pendingAdRequestContext; private List supportedMimeTypes; - @Nullable private EventListener eventListener; - @Nullable private Player player; + @Nullable + private EventListener eventListener; + @Nullable + private Player player; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercentage; @@ -347,14 +366,23 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking IMA's state. - /** The expected ad group index that IMA should load next. */ + /** + * The expected ad group index that IMA should load next. + */ private int expectedAdGroupIndex; - /** The index of the current ad group that IMA is loading. */ + /** + * The index of the current ad group that IMA is loading. + */ private int adGroupIndex; - /** Whether IMA has sent an ad event to pause content since the last resume content event. */ + /** + * Whether IMA has sent an ad event to pause content since the last resume content event. + */ private boolean imaPausedContent; - /** The current ad playback state. */ - private @ImaAdState int imaAdState; + /** + * The current ad playback state. + */ + private @ImaAdState + int imaAdState; /** * Whether {@link com.google.ads.interactivemedia.v3.api.AdsLoader#contentComplete()} has been * called since starting ad playback. @@ -363,7 +391,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking the player/loader state. - /** Whether the player is playing an ad. */ + /** + * Whether the player is playing an ad. + */ private boolean playingAd; /** * If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET} @@ -386,9 +416,13 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * content progress should increase. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressOffsetMs; - /** Stores the pending content position when a seek operation was intercepted to play an ad. */ + /** + * Stores the pending content position when a seek operation was intercepted to play an ad. + */ private long pendingContentPositionMs; - /** Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. */ + /** + * Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. + */ private boolean sentPendingContentPositionMs; /** @@ -396,10 +430,10 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * *

If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead. * - * @param context The context. + * @param context The context. * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. */ public ImaAdsLoader(Context context, Uri adTagUri) { this( @@ -419,12 +453,13 @@ public ImaAdsLoader(Context context, Uri adTagUri) { /** * Creates a new IMA ads loader. * - * @param context The context. - * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * @param context The context. + * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. * @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to - * use the default settings. If set, the player type and version fields may be overwritten. + * use the default settings. If set, the player type and version fields may + * be overwritten. * @deprecated Use {@link ImaAdsLoader.Builder}. */ @Deprecated @@ -489,8 +524,8 @@ private ImaAdsLoader( } /** - * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by - * this instance. + * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by this + * instance. */ public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() { return adsLoader; @@ -1368,7 +1403,7 @@ private void maybeNotifyInternalError(String name, Exception cause) { private static long[] getAdGroupTimesUs(List cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. - return new long[] {0}; + return new long[]{0}; } int count = cuePoints.size(); @@ -1406,24 +1441,44 @@ private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) { } } - /** Factory for objects provided by the IMA SDK. */ + /** + * Factory for objects provided by the IMA SDK. + */ @VisibleForTesting - /* package */ interface ImaFactory { - /** @see ImaSdkSettings */ + /* package */ interface ImaFactory { + + /** + * @see ImaSdkSettings + */ ImaSdkSettings createImaSdkSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() + */ AdsRenderingSettings createAdsRenderingSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() + */ AdDisplayContainer createAdDisplayContainer(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() + */ AdsRequest createAdsRequest(); - /** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */ + + /** + * @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) + */ com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer); } - /** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */ + /** + * Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. + */ private static final class DefaultImaFactory implements ImaFactory { + @Override public ImaSdkSettings createImaSdkSettings() { return ImaSdkFactory.getInstance().createImaSdkSettings(); diff --git a/automatedtests/src/androidTestR2_10_6/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java b/automatedtests/src/androidTestR2_10_6/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java index 229344cd..2f09221e 100644 --- a/automatedtests/src/androidTestR2_10_6/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java +++ b/automatedtests/src/androidTestR2_10_6/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java @@ -5,10 +5,7 @@ import android.net.Uri; import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; -import android.view.View; - import androidx.annotation.Nullable; - import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -24,151 +21,151 @@ 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.RandomTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.ui.PlayerNotificationManager; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Util; import com.mux.stats.sdk.muxstats.automatedtests.R; - import java.lang.reflect.Constructor; public class SimplePlayerTestActivity extends SimplePlayerBaseActivity { - private boolean isShowingTrackSelectionDialog; - - public void initExoPlayer() { - // Hopfully this will not channge the track selection set programmatically - TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory( - AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS * 10, - AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS * 10, - AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, - AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION - ); - DefaultTrackSelector.Parameters trackSelectorParameters - = new DefaultTrackSelector.ParametersBuilder().build(); - trackSelector = new DefaultTrackSelector(trackSelectionFactory); - trackSelector.setParameters(trackSelectorParameters); - RenderersFactory renderersFactory = new DefaultRenderersFactory(/* context= */ this); - player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector); - } - - // This is for background playback, set appropriate notification and etc - public void initAudioSession() { - notificationManager = PlayerNotificationManager.createWithNotificationChannel( - getApplicationContext(), - PLAYBACK_CHANNEL_ID, - R.string.channel_name, - R.string.channel_description, - PLAYBACK_NOTIFICATION_ID, - new MDAdapter(), - new CustomNotificationListener() - ); - notificationManager.setUseNavigationActions(false); - notificationManager.setUseStopAction(true); - notificationManager.setPlayer(player); - - mediaSessionCompat = new MediaSessionCompat(this, "hello_world_media"); - notificationManager.setMediaSessionToken(mediaSessionCompat.getSessionToken()); - mediaSessionConnector = new MediaSessionConnector(mediaSessionCompat); - mediaSessionConnector.setPlayer(player); + private boolean isShowingTrackSelectionDialog; + + public void initExoPlayer() { + // Hopfully this will not channge the track selection set programmatically + TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory( + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS * 10, + AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS * 10, + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, + AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION + ); + DefaultTrackSelector.Parameters trackSelectorParameters + = new DefaultTrackSelector.ParametersBuilder().build(); + trackSelector = new DefaultTrackSelector(trackSelectionFactory); + trackSelector.setParameters(trackSelectorParameters); + RenderersFactory renderersFactory = new DefaultRenderersFactory(/* context= */ this); + player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector); + } + + // This is for background playback, set appropriate notification and etc + public void initAudioSession() { + notificationManager = PlayerNotificationManager.createWithNotificationChannel( + getApplicationContext(), + PLAYBACK_CHANNEL_ID, + R.string.channel_name, + R.string.channel_description, + PLAYBACK_NOTIFICATION_ID, + new MDAdapter(), + new CustomNotificationListener() + ); + notificationManager.setUseNavigationActions(false); + notificationManager.setUseStopAction(true); + notificationManager.setPlayer(player); + + mediaSessionCompat = new MediaSessionCompat(this, "hello_world_media"); + notificationManager.setMediaSessionToken(mediaSessionCompat.getSessionToken()); + mediaSessionConnector = new MediaSessionConnector(mediaSessionCompat); + mediaSessionConnector.setPlayer(player); + } + + public DataSource.Factory buildDataSourceFactory() { + return new DefaultDataSourceFactory(this, "Android-automated_tests"); + } + + public MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) { + DataSource.Factory dataSourceFactory = buildDataSourceFactory(); + @C.ContentType int type = Util.inferContentType(uri, overrideExtension); + switch (type) { + case C.TYPE_DASH: + return new DashMediaSource.Factory(dataSourceFactory).createMediaSource(uri); + case C.TYPE_SS: + return new SsMediaSource.Factory(dataSourceFactory).createMediaSource(uri); + case C.TYPE_HLS: + return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri); + case C.TYPE_OTHER: + return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri); + default: + throw new IllegalStateException("Unsupported type: " + type); } + } + + private MediaSource buildMediaSource(Uri uri) { + return buildMediaSource(uri, null); + } + + /** + * Returns an ads media source, reusing the ads loader if one exists. + */ + public MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { + // Load the extension source using reflection so the demo app doesn't have to depend on it. + // The ads loader is reused for multiple playbacks, so that ad playback can resume. + try { + Class loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader"); + if (adsLoader == null) { + // Full class names used so the LINT.IfChange rule triggers should any of the classes move. + // LINT.IfChange + Constructor loaderConstructor = + loaderClass + .asSubclass(AdsLoader.class) + .getConstructor(android.content.Context.class, android.net.Uri.class); + // LINT.ThenChange(../../../../../../../../proguard-rules.txt) + adsLoader = loaderConstructor.newInstance(this, adTagUri); + } + adsLoader.setPlayer(player); + AdsMediaSource.MediaSourceFactory adMediaSourceFactory = + new AdsMediaSource.MediaSourceFactory() { + @Override + public MediaSource createMediaSource(Uri uri) { + return SimplePlayerTestActivity.this.buildMediaSource(uri); + } - public DataSource.Factory buildDataSourceFactory() { - return new DefaultDataSourceFactory(this, "Android-automated_tests"); + @Override + public int[] getSupportedTypes() { + return new int[]{C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; + } + }; + + // Because of how this is loaded via reflection, we know that this will be + // a ImaAdsLoader, so cast it over so that we can get a reference to the + // real IMA AdsLoader instance. + muxStats.monitorImaAdsLoader(((ImaAdsLoader) adsLoader).getAdsLoader()); + return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView); + } catch (ClassNotFoundException e) { + // IMA extension not loaded. + return null; + } catch (Exception e) { + throw new RuntimeException(e); } - - public MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) { - DataSource.Factory dataSourceFactory = buildDataSourceFactory(); - @C.ContentType int type = Util.inferContentType(uri, overrideExtension); - switch (type) { - case C.TYPE_DASH: - return new DashMediaSource.Factory(dataSourceFactory).createMediaSource(uri); - case C.TYPE_SS: - return new SsMediaSource.Factory(dataSourceFactory).createMediaSource(uri); - case C.TYPE_HLS: - return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri); - case C.TYPE_OTHER: - return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri); - default: - throw new IllegalStateException("Unsupported type: " + type); - } + } + + @Override + public void startPlayback() { + Uri testUri = Uri.parse(urlToPlay); + testMediaSource = buildMediaSource(testUri, null); + if (loadedAdTagUri != null) { + testMediaSource = createAdsMediaSource(testMediaSource, loadedAdTagUri); } - private MediaSource buildMediaSource(Uri uri) { - return buildMediaSource(uri, null); - } + player.setPlayWhenReady(true); + player.prepare(testMediaSource); + } - /** Returns an ads media source, reusing the ads loader if one exists. */ - public MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { - // Load the extension source using reflection so the demo app doesn't have to depend on it. - // The ads loader is reused for multiple playbacks, so that ad playback can resume. - try { - Class loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader"); - if (adsLoader == null) { - // Full class names used so the LINT.IfChange rule triggers should any of the classes move. - // LINT.IfChange - Constructor loaderConstructor = - loaderClass - .asSubclass(AdsLoader.class) - .getConstructor(android.content.Context.class, android.net.Uri.class); - // LINT.ThenChange(../../../../../../../../proguard-rules.txt) - adsLoader = loaderConstructor.newInstance(this, adTagUri); - } - adsLoader.setPlayer(player); - AdsMediaSource.MediaSourceFactory adMediaSourceFactory = - new AdsMediaSource.MediaSourceFactory() { - @Override - public MediaSource createMediaSource(Uri uri) { - return SimplePlayerTestActivity.this.buildMediaSource(uri); - } - - @Override - public int[] getSupportedTypes() { - return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; - } - }; - - // Because of how this is loaded via reflection, we know that this will be - // a ImaAdsLoader, so cast it over so that we can get a reference to the - // real IMA AdsLoader instance. - muxStats.monitorImaAdsLoader(((ImaAdsLoader) adsLoader).getAdsLoader()); - return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView); - } catch (ClassNotFoundException e) { - // IMA extension not loaded. - return null; - } catch (Exception e) { - throw new RuntimeException(e); - } - } + // For background audio playback + class CustomNotificationListener implements PlayerNotificationManager.NotificationListener { @Override - public void startPlayback() { - Uri testUri = Uri.parse(urlToPlay); - testMediaSource = buildMediaSource(testUri, null); - if (loadedAdTagUri != null) { - testMediaSource = createAdsMediaSource(testMediaSource, loadedAdTagUri); - } - - player.setPlayWhenReady(true); - player.prepare( testMediaSource ); + public void onNotificationCancelled(int notificationId, boolean dismissedByUser) { + // TODO implement this + Log.e(TAG, "onNotificationCancelled"); } - // For background audio playback - class CustomNotificationListener implements PlayerNotificationManager.NotificationListener { - - @Override - public void onNotificationCancelled(int notificationId, boolean dismissedByUser) { - // TODO implement this - Log.e(TAG, "onNotificationCancelled"); - } - - @Override - public void onNotificationPosted(int notificationId, Notification notification, boolean ongoing) { - // TODO implement this - Log.e(TAG, "onNotificationPosted"); - } + @Override + public void onNotificationPosted(int notificationId, Notification notification, + boolean ongoing) { + // TODO implement this + Log.e(TAG, "onNotificationPosted"); } + } } diff --git a/automatedtests/src/androidTestR2_11_1/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/automatedtests/src/androidTestR2_11_1/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 98dbef7c..ac3cb4a4 100644 --- a/automatedtests/src/androidTestR2_11_1/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/automatedtests/src/androidTestR2_11_1/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -81,31 +81,36 @@ * #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling * {@link #release()}. * - *

The IMA SDK can take into account video control overlay views when calculating ad viewability. - * For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} and {@link - * AdViewProvider#getAdOverlayViews()}. + *

The IMA SDK can take into account video control overlay views when calculating ad + * viewability. For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} + * and {@link AdViewProvider#getAdOverlayViews()}. */ public final class ImaAdsLoader implements Player.EventListener, - AdsLoader, - VideoAdPlayer, - ContentProgressProvider, - AdErrorListener, - AdsLoadedListener, - AdEventListener { + AdsLoader, + VideoAdPlayer, + ContentProgressProvider, + AdErrorListener, + AdsLoadedListener, + AdEventListener { static { ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); } - /** Builder for {@link ImaAdsLoader}. */ + /** + * Builder for {@link ImaAdsLoader}. + */ public static final class Builder { private final Context context; - @Nullable private ImaSdkSettings imaSdkSettings; - @Nullable private AdEventListener adEventListener; - @Nullable private Set adUiElements; + @Nullable + private ImaSdkSettings imaSdkSettings; + @Nullable + private AdEventListener adEventListener; + @Nullable + private Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; private int mediaBitrate; @@ -141,8 +146,7 @@ public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { } /** - * Sets a listener for ad events that will be passed to {@link - * AdsManager#addAdEventListener(AdEventListener)}. + * Sets a listener for ad events that will be passed to {@link AdsManager#addAdEventListener(AdEventListener)}. * * @param adEventListener The ad event listener. * @return This builder, for convenience. @@ -208,7 +212,7 @@ public Builder setMaxMediaBitrate(int bitrate) { * setting is {@code true}. * * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on - * Android TV devices. + * Android TV devices. * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ @@ -218,7 +222,7 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila } @VisibleForTesting - /* package */ Builder setImaFactory(ImaFactory imaFactory) { + /* package */ Builder setImaFactory(ImaFactory imaFactory) { this.imaFactory = Assertions.checkNotNull(imaFactory); return this; } @@ -226,9 +230,8 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila /** * Returns a new {@link ImaAdsLoader} for the specified ad tag. * - * @param adTagUri The URI of a compatible ad tag to load. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * information on compatible ad tags. + * @param adTagUri The URI of a compatible ad tag to load. See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for information on compatible ad tags. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdTag(Uri adTagUri) { @@ -250,7 +253,7 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) { * Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response. * * @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of - * making a request via an ad tag URL. + * making a request via an ad tag URL. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdsResponse(String adsResponse) { @@ -280,7 +283,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private static final String IMA_SDK_SETTINGS_PLAYER_TYPE = "google/exo.ext.ima"; private static final String IMA_SDK_SETTINGS_PLAYER_VERSION = ExoPlayerLibraryInfo.VERSION; - /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ + /** + * The value used in {@link VideoProgressUpdate}s to indicate an unset duration. + */ private static final long IMA_DURATION_UNSET = -1L; /** @@ -289,17 +294,24 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final long END_OF_CONTENT_POSITION_THRESHOLD_MS = 5000; - /** The maximum duration before an ad break that IMA may start preloading the next ad. */ + /** + * The maximum duration before an ad break that IMA may start preloading the next ad. + */ private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000; private static final int TIMEOUT_UNSET = -1; private static final int BITRATE_UNSET = -1; - /** The state of ad playback. */ + /** + * The state of ad playback. + */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) - private @interface ImaAdState {} + private @interface ImaAdState { + + } + /** * The ad playback state when IMA is not playing an ad. */ @@ -313,14 +325,18 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final int IMA_AD_STATE_PAUSED = 2; - @Nullable private final Uri adTagUri; - @Nullable private final String adsResponse; + @Nullable + private final Uri adTagUri; + @Nullable + private final String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; private final boolean focusSkipButtonWhenAvailable; private final int mediaBitrate; - @Nullable private final Set adUiElements; - @Nullable private final AdEventListener adEventListener; + @Nullable + private final Set adUiElements; + @Nullable + private final AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; private final List adCallbacks; @@ -328,11 +344,14 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader; private boolean wasSetPlayerCalled; - @Nullable private Player nextPlayer; + @Nullable + private Player nextPlayer; private Object pendingAdRequestContext; private List supportedMimeTypes; - @Nullable private EventListener eventListener; - @Nullable private Player player; + @Nullable + private EventListener eventListener; + @Nullable + private Player player; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercentage; @@ -347,14 +366,23 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking IMA's state. - /** The expected ad group index that IMA should load next. */ + /** + * The expected ad group index that IMA should load next. + */ private int expectedAdGroupIndex; - /** The index of the current ad group that IMA is loading. */ + /** + * The index of the current ad group that IMA is loading. + */ private int adGroupIndex; - /** Whether IMA has sent an ad event to pause content since the last resume content event. */ + /** + * Whether IMA has sent an ad event to pause content since the last resume content event. + */ private boolean imaPausedContent; - /** The current ad playback state. */ - private @ImaAdState int imaAdState; + /** + * The current ad playback state. + */ + private @ImaAdState + int imaAdState; /** * Whether {@link com.google.ads.interactivemedia.v3.api.AdsLoader#contentComplete()} has been * called since starting ad playback. @@ -363,7 +391,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking the player/loader state. - /** Whether the player is playing an ad. */ + /** + * Whether the player is playing an ad. + */ private boolean playingAd; /** * If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET} @@ -386,9 +416,13 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * content progress should increase. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressOffsetMs; - /** Stores the pending content position when a seek operation was intercepted to play an ad. */ + /** + * Stores the pending content position when a seek operation was intercepted to play an ad. + */ private long pendingContentPositionMs; - /** Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. */ + /** + * Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. + */ private boolean sentPendingContentPositionMs; /** @@ -396,10 +430,10 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * *

If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead. * - * @param context The context. + * @param context The context. * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. */ public ImaAdsLoader(Context context, Uri adTagUri) { this( @@ -419,12 +453,13 @@ public ImaAdsLoader(Context context, Uri adTagUri) { /** * Creates a new IMA ads loader. * - * @param context The context. - * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * @param context The context. + * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. * @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to - * use the default settings. If set, the player type and version fields may be overwritten. + * use the default settings. If set, the player type and version fields may + * be overwritten. * @deprecated Use {@link ImaAdsLoader.Builder}. */ @Deprecated @@ -489,8 +524,8 @@ private ImaAdsLoader( } /** - * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by - * this instance. + * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by this + * instance. */ public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() { return adsLoader; @@ -1372,7 +1407,7 @@ private void maybeNotifyInternalError(String name, Exception cause) { private static long[] getAdGroupTimesUs(List cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. - return new long[] {0}; + return new long[]{0}; } int count = cuePoints.size(); @@ -1410,24 +1445,44 @@ private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) { } } - /** Factory for objects provided by the IMA SDK. */ + /** + * Factory for objects provided by the IMA SDK. + */ @VisibleForTesting - /* package */ interface ImaFactory { - /** @see ImaSdkSettings */ + /* package */ interface ImaFactory { + + /** + * @see ImaSdkSettings + */ ImaSdkSettings createImaSdkSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() + */ AdsRenderingSettings createAdsRenderingSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() + */ AdDisplayContainer createAdDisplayContainer(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() + */ AdsRequest createAdsRequest(); - /** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */ + + /** + * @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) + */ com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer); } - /** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */ + /** + * Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. + */ private static final class DefaultImaFactory implements ImaFactory { + @Override public ImaSdkSettings createImaSdkSettings() { return ImaSdkFactory.getInstance().createImaSdkSettings(); diff --git a/automatedtests/src/androidTestR2_11_1/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java b/automatedtests/src/androidTestR2_11_1/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java index af3f24b4..9f80011c 100644 --- a/automatedtests/src/androidTestR2_11_1/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java +++ b/automatedtests/src/androidTestR2_11_1/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java @@ -5,9 +5,7 @@ import android.net.Uri; import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; - import androidx.annotation.Nullable; - import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -26,157 +24,158 @@ 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.RandomTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.ui.PlayerNotificationManager; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Util; import com.mux.stats.sdk.muxstats.automatedtests.R; - import java.lang.reflect.Constructor; public class SimplePlayerTestActivity extends SimplePlayerBaseActivity { - public void initExoPlayer() { - // Hopfully this will not channge the track selection set programmatically - TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory( - AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS * 10, - AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS * 10, - AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, - AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION - ); - DefaultTrackSelector.Parameters trackSelectorParameters - = new DefaultTrackSelector.ParametersBuilder( this ).build(); - trackSelector = new DefaultTrackSelector( this, trackSelectionFactory ); - trackSelector.setParameters(trackSelectorParameters); - RenderersFactory renderersFactory = new DefaultRenderersFactory(/* context= */ this); - player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector); - } - - // This is for background playback, set appropriate notification and etc - public void initAudioSession() { - notificationManager = PlayerNotificationManager.createWithNotificationChannel( - getApplicationContext(), - PLAYBACK_CHANNEL_ID, - R.string.channel_name, - R.string.channel_description, - PLAYBACK_NOTIFICATION_ID, - new MDAdapter(), - new CustomNotificationListener() - ); - notificationManager.setUseNavigationActions(false); - notificationManager.setUseStopAction(true); - notificationManager.setPlayer(player); - - mediaSessionCompat = new MediaSessionCompat(this, "hello_world_media"); - notificationManager.setMediaSessionToken(mediaSessionCompat.getSessionToken()); - mediaSessionConnector = new MediaSessionConnector(mediaSessionCompat); - mediaSessionConnector.setPlayer(player); + public void initExoPlayer() { + // Hopfully this will not channge the track selection set programmatically + TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory( + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS * 10, + AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS * 10, + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, + AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION + ); + DefaultTrackSelector.Parameters trackSelectorParameters + = new DefaultTrackSelector.ParametersBuilder(this).build(); + trackSelector = new DefaultTrackSelector(this, trackSelectionFactory); + trackSelector.setParameters(trackSelectorParameters); + RenderersFactory renderersFactory = new DefaultRenderersFactory(/* context= */ this); + player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector); + } + + // This is for background playback, set appropriate notification and etc + public void initAudioSession() { + notificationManager = PlayerNotificationManager.createWithNotificationChannel( + getApplicationContext(), + PLAYBACK_CHANNEL_ID, + R.string.channel_name, + R.string.channel_description, + PLAYBACK_NOTIFICATION_ID, + new MDAdapter(), + new CustomNotificationListener() + ); + notificationManager.setUseNavigationActions(false); + notificationManager.setUseStopAction(true); + notificationManager.setPlayer(player); + + mediaSessionCompat = new MediaSessionCompat(this, "hello_world_media"); + notificationManager.setMediaSessionToken(mediaSessionCompat.getSessionToken()); + mediaSessionConnector = new MediaSessionConnector(mediaSessionCompat); + mediaSessionConnector.setPlayer(player); + } + + public DataSource.Factory buildDataSourceFactory() { + return new DefaultDataSourceFactory(this, "Android-automated_tests"); + } + + private MediaSource createLeafMediaSource( + Uri uri, String extension, DrmSessionManager drmSessionManager) { + @C.ContentType int type = Util.inferContentType(uri, extension); + DataSource.Factory dataSourceFactory = buildDataSourceFactory(); + switch (type) { + case C.TYPE_DASH: + return new DashMediaSource.Factory(dataSourceFactory) + .setDrmSessionManager(drmSessionManager) + .createMediaSource(uri); + case C.TYPE_SS: + return new SsMediaSource.Factory(dataSourceFactory) + .setDrmSessionManager(drmSessionManager) + .createMediaSource(uri); + case C.TYPE_HLS: + return new HlsMediaSource.Factory(dataSourceFactory) + .setDrmSessionManager(drmSessionManager) + .createMediaSource(uri); + case C.TYPE_OTHER: + return new ProgressiveMediaSource.Factory(dataSourceFactory) + .setDrmSessionManager(drmSessionManager) + .createMediaSource(uri); + default: + throw new IllegalStateException("Unsupported type: " + type); } + } + + public MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) { + return createLeafMediaSource(uri, overrideExtension, + DrmSessionManager.getDummyDrmSessionManager()); + } + + /** + * Returns an ads media source, reusing the ads loader if one exists. + */ + public MediaSource createAdsMediaSource(MediaSource aMediaSource, Uri adTagUri) { + // Load the extension source using reflection so the demo app doesn't have to depend on it. + // The ads loader is reused for multiple playbacks, so that ad playback can resume. + try { + Class loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader"); + if (adsLoader == null) { + // Full class names used so the LINT.IfChange rule triggers should any of the classes move. + // LINT.IfChange + Constructor loaderConstructor = + loaderClass + .asSubclass(AdsLoader.class) + .getConstructor(android.content.Context.class, android.net.Uri.class); + // LINT.ThenChange(../../../../../../../../proguard-rules.txt) + adsLoader = loaderConstructor.newInstance(this, adTagUri); + } + MediaSourceFactory adMediaSourceFactory = + new MediaSourceFactory() { + @Override + public MediaSource createMediaSource(Uri uri) { + return SimplePlayerTestActivity.this.createLeafMediaSource( + uri, /* extension=*/ null, DrmSessionManager.getDummyDrmSessionManager()); + } - public DataSource.Factory buildDataSourceFactory() { - return new DefaultDataSourceFactory(this, "Android-automated_tests"); + @Override + public int[] getSupportedTypes() { + return new int[]{C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; + } + }; + // Because of how this is loaded via reflection, we know that this will be + // a ImaAdsLoader, so cast it over so that we can get a reference to the + // real IMA AdsLoader instance. + ((ImaAdsLoader) adsLoader).setPlayer(player); + muxStats.monitorImaAdsLoader(((ImaAdsLoader) adsLoader).getAdsLoader()); + return new AdsMediaSource(aMediaSource, adMediaSourceFactory, adsLoader, playerView); + } catch (ClassNotFoundException e) { + // IMA extension not loaded. + return null; + } catch (Exception e) { + throw new RuntimeException(e); } - - private MediaSource createLeafMediaSource( - Uri uri, String extension, DrmSessionManager drmSessionManager) { - @C.ContentType int type = Util.inferContentType(uri, extension); - DataSource.Factory dataSourceFactory = buildDataSourceFactory(); - switch (type) { - case C.TYPE_DASH: - return new DashMediaSource.Factory(dataSourceFactory) - .setDrmSessionManager(drmSessionManager) - .createMediaSource(uri); - case C.TYPE_SS: - return new SsMediaSource.Factory(dataSourceFactory) - .setDrmSessionManager(drmSessionManager) - .createMediaSource(uri); - case C.TYPE_HLS: - return new HlsMediaSource.Factory(dataSourceFactory) - .setDrmSessionManager(drmSessionManager) - .createMediaSource(uri); - case C.TYPE_OTHER: - return new ProgressiveMediaSource.Factory(dataSourceFactory) - .setDrmSessionManager(drmSessionManager) - .createMediaSource(uri); - default: - throw new IllegalStateException("Unsupported type: " + type); - } + } + + @Override + public void startPlayback() { + Uri testUri = Uri.parse(urlToPlay); + testMediaSource = buildMediaSource(testUri, null); + if (loadedAdTagUri != null) { + testMediaSource = createAdsMediaSource(testMediaSource, loadedAdTagUri); } - public MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) { - return createLeafMediaSource(uri, overrideExtension, DrmSessionManager.getDummyDrmSessionManager()); - } + player.setPlayWhenReady(true); + player.prepare(testMediaSource); + } - /** Returns an ads media source, reusing the ads loader if one exists. */ - public MediaSource createAdsMediaSource(MediaSource aMediaSource, Uri adTagUri) { - // Load the extension source using reflection so the demo app doesn't have to depend on it. - // The ads loader is reused for multiple playbacks, so that ad playback can resume. - try { - Class loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader"); - if (adsLoader == null) { - // Full class names used so the LINT.IfChange rule triggers should any of the classes move. - // LINT.IfChange - Constructor loaderConstructor = - loaderClass - .asSubclass(AdsLoader.class) - .getConstructor(android.content.Context.class, android.net.Uri.class); - // LINT.ThenChange(../../../../../../../../proguard-rules.txt) - adsLoader = loaderConstructor.newInstance(this, adTagUri); - } - MediaSourceFactory adMediaSourceFactory = - new MediaSourceFactory() { - @Override - public MediaSource createMediaSource(Uri uri) { - return SimplePlayerTestActivity.this.createLeafMediaSource( - uri, /* extension=*/ null, DrmSessionManager.getDummyDrmSessionManager()); - } - - @Override - public int[] getSupportedTypes() { - return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; - } - }; - // Because of how this is loaded via reflection, we know that this will be - // a ImaAdsLoader, so cast it over so that we can get a reference to the - // real IMA AdsLoader instance. - ((ImaAdsLoader) adsLoader).setPlayer(player); - muxStats.monitorImaAdsLoader(((ImaAdsLoader) adsLoader).getAdsLoader()); - return new AdsMediaSource(aMediaSource, adMediaSourceFactory, adsLoader, playerView); - } catch (ClassNotFoundException e) { - // IMA extension not loaded. - return null; - } catch (Exception e) { - throw new RuntimeException(e); - } - } + class CustomNotificationListener implements PlayerNotificationManager.NotificationListener { @Override - public void startPlayback() { - Uri testUri = Uri.parse(urlToPlay); - testMediaSource = buildMediaSource(testUri, null); - if (loadedAdTagUri != null) { - testMediaSource = createAdsMediaSource(testMediaSource, loadedAdTagUri); - } - - player.setPlayWhenReady(true); - player.prepare( testMediaSource ); + public void onNotificationCancelled(int notificationId, boolean dismissedByUser) { + // TODO implement this + Log.e(TAG, "onNotificationCancelled"); } - class CustomNotificationListener implements PlayerNotificationManager.NotificationListener { - - @Override - public void onNotificationCancelled(int notificationId, boolean dismissedByUser) { - // TODO implement this - Log.e(TAG, "onNotificationCancelled"); - } - - @Override - public void onNotificationPosted(int notificationId, Notification notification, boolean ongoing) { - // TODO implement this - Log.e(TAG, "onNotificationPosted"); - } + @Override + public void onNotificationPosted(int notificationId, Notification notification, + boolean ongoing) { + // TODO implement this + Log.e(TAG, "onNotificationPosted"); } + } } diff --git a/automatedtests/src/androidTestR2_12_1/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/automatedtests/src/androidTestR2_12_1/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 4185a158..d79153d8 100644 --- a/automatedtests/src/androidTestR2_12_1/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/automatedtests/src/androidTestR2_12_1/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -115,7 +115,9 @@ public final class ImaAdsLoader ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); } - /** Builder for {@link ImaAdsLoader}. */ + /** + * Builder for {@link ImaAdsLoader}. + */ public static final class Builder { /** @@ -131,13 +133,20 @@ public static final class Builder { private final Context context; - @Nullable private ImaSdkSettings imaSdkSettings; - @Nullable private AdErrorListener adErrorListener; - @Nullable private AdEventListener adEventListener; - @Nullable private VideoAdPlayer.VideoAdPlayerCallback videoAdPlayerCallback; - @Nullable private List adMediaMimeTypes; - @Nullable private Set adUiElements; - @Nullable private Collection companionAdSlots; + @Nullable + private ImaSdkSettings imaSdkSettings; + @Nullable + private AdErrorListener adErrorListener; + @Nullable + private AdEventListener adEventListener; + @Nullable + private VideoAdPlayer.VideoAdPlayerCallback videoAdPlayerCallback; + @Nullable + private List adMediaMimeTypes; + @Nullable + private Set adUiElements; + @Nullable + private Collection companionAdSlots; private long adPreloadTimeoutMs; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; @@ -178,9 +187,8 @@ public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { } /** - * Sets a listener for ad errors that will be passed to {@link - * AdsLoader#addAdErrorListener(AdErrorListener)} and {@link - * AdsManager#addAdErrorListener(AdErrorListener)}. + * Sets a listener for ad errors that will be passed to {@link AdsLoader#addAdErrorListener(AdErrorListener)} + * and {@link AdsManager#addAdErrorListener(AdErrorListener)}. * * @param adErrorListener The ad error listener. * @return This builder, for convenience. @@ -191,8 +199,7 @@ public Builder setAdErrorListener(AdErrorListener adErrorListener) { } /** - * Sets a listener for ad events that will be passed to {@link - * AdsManager#addAdEventListener(AdEventListener)}. + * Sets a listener for ad events that will be passed to {@link AdsManager#addAdEventListener(AdEventListener)}. * * @param adEventListener The ad event listener. * @return This builder, for convenience. @@ -248,9 +255,10 @@ public Builder setCompanionAdSlots(Collection companionAdSlots) * AdsMediaSource} will be used. * * @param adMediaMimeTypes The MIME types to prioritize for linear ad media. May contain {@link - * MimeTypes#APPLICATION_MPD}, {@link MimeTypes#APPLICATION_M3U8}, {@link - * MimeTypes#VIDEO_MP4}, {@link MimeTypes#VIDEO_WEBM}, {@link MimeTypes#VIDEO_H263}, {@link - * MimeTypes#AUDIO_MP4} and {@link MimeTypes#AUDIO_MPEG}. + * MimeTypes#APPLICATION_MPD}, {@link MimeTypes#APPLICATION_M3U8}, + * {@link MimeTypes#VIDEO_MP4}, {@link MimeTypes#VIDEO_WEBM}, {@link + * MimeTypes#VIDEO_H263}, {@link MimeTypes#AUDIO_MP4} and {@link + * MimeTypes#AUDIO_MPEG}. * @return This builder, for convenience. * @see AdsRenderingSettings#setMimeTypes(List) */ @@ -265,11 +273,11 @@ public Builder setAdMediaMimeTypes(List adMediaMimeTypes) { * C#TIME_UNSET} if there should be no such timeout. The default value is {@value * #DEFAULT_AD_PRELOAD_TIMEOUT_MS} ms. * - *

The purpose of this timeout is to avoid playback getting stuck in the unexpected case that - * the IMA SDK does not load an ad break based on the player's reported content position. + *

The purpose of this timeout is to avoid playback getting stuck in the unexpected case + * that the IMA SDK does not load an ad break based on the player's reported content position. * * @param adPreloadTimeoutMs The timeout buffering duration in milliseconds, or {@link - * C#TIME_UNSET} for no timeout. + * C#TIME_UNSET} for no timeout. * @return This builder, for convenience. */ public Builder setAdPreloadTimeoutMs(long adPreloadTimeoutMs) { @@ -322,7 +330,7 @@ public Builder setMaxMediaBitrate(int bitrate) { * setting is {@code true}. * * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on - * Android TV devices. + * Android TV devices. * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ @@ -338,7 +346,7 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila * setting is {@code true}. * * @param playAdBeforeStartPosition Whether to play an ad before the start position when - * beginning playback. + * beginning playback. * @return This builder, for convenience. */ public Builder setPlayAdBeforeStartPosition(boolean playAdBeforeStartPosition) { @@ -352,7 +360,7 @@ public Builder setPlayAdBeforeStartPosition(boolean playAdBeforeStartPosition) { * enabled in production applications. * * @param debugModeEnabled Whether to enable outputting verbose logs for the IMA extension and - * IMA SDK. + * IMA SDK. * @return This builder, for convenience. * @see ImaSdkSettings#setDebugMode(boolean) */ @@ -362,7 +370,7 @@ public Builder setDebugModeEnabled(boolean debugModeEnabled) { } @VisibleForTesting - /* package */ Builder setImaFactory(ImaUtil.ImaFactory imaFactory) { + /* package */ Builder setImaFactory(ImaUtil.ImaFactory imaFactory) { this.imaFactory = checkNotNull(imaFactory); return this; } @@ -370,13 +378,12 @@ public Builder setDebugModeEnabled(boolean debugModeEnabled) { /** * Returns a new {@link ImaAdsLoader} for the specified ad tag. * - * @param adTagUri The URI of a compatible ad tag to load. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * information on compatible ad tags. + * @param adTagUri The URI of a compatible ad tag to load. See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for information on compatible ad tags. * @return The new {@link ImaAdsLoader}. * @deprecated Pass the ad tag URI when setting media item playback properties (if using the - * media item API) or as a {@link DataSpec} when constructing the {@link AdsMediaSource} (if - * using media sources directly). + * media item API) or as a {@link DataSpec} when constructing the {@link AdsMediaSource} (if + * using media sources directly). */ @Deprecated public ImaAdsLoader buildForAdTag(Uri adTagUri) { @@ -392,13 +399,13 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) { * Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response. * * @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of - * making a request via an ad tag URL. + * making a request via an ad tag URL. * @return The new {@link ImaAdsLoader}. * @deprecated Pass the ads response as a data URI when setting media item playback properties - * (if using the media item API) or as a {@link DataSpec} when constructing the {@link - * AdsMediaSource} (if using media sources directly). {@link - * Util#getDataUriForString(String, String)} can be used to construct a data URI from - * literal string ads response (with MIME type text/xml). + * (if using the media item API) or as a {@link DataSpec} when constructing the {@link + * AdsMediaSource} (if using media sources directly). {@link Util#getDataUriForString(String, + * String)} can be used to construct a data URI from literal string ads response (with MIME type + * text/xml). */ @Deprecated public ImaAdsLoader buildForAdsResponse(String adsResponse) { @@ -406,7 +413,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { context, getConfiguration(), imaFactory, /* adTagUri= */ null, adsResponse); } - /** Returns a new {@link ImaAdsLoader}. */ + /** + * Returns a new {@link ImaAdsLoader}. + */ public ImaAdsLoader build() { return new ImaAdsLoader( context, getConfiguration(), imaFactory, /* adTagUri= */ null, /* adsResponse= */ null); @@ -445,7 +454,9 @@ public ImaAdsLoader build() { */ private static final int AD_PROGRESS_UPDATE_INTERVAL_MS = 100; - /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ + /** + * The value used in {@link VideoProgressUpdate}s to indicate an unset duration. + */ private static final long IMA_DURATION_UNSET = -1L; /** @@ -458,18 +469,27 @@ public ImaAdsLoader build() { * milliseconds. */ private static final long THRESHOLD_AD_PRELOAD_MS = 4000; - /** The threshold below which ad cue points are treated as matching, in microseconds. */ + /** + * The threshold below which ad cue points are treated as matching, in microseconds. + */ private static final long THRESHOLD_AD_MATCH_US = 1000; private static final int TIMEOUT_UNSET = -1; private static final int BITRATE_UNSET = -1; - /** The state of ad playback. */ + /** + * The state of ad playback. + */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) - private @interface ImaAdState {} - /** The ad playback state when IMA is not playing an ad. */ + private @interface ImaAdState { + + } + + /** + * The ad playback state when IMA is not playing an ad. + */ private static final int IMA_AD_STATE_NONE = 0; /** * The ad playback state when IMA has called {@link ComponentListener#playAd(AdMediaInfo)} and not @@ -487,8 +507,10 @@ public ImaAdsLoader build() { private final ImaUtil.Configuration configuration; private final Context context; private final ImaUtil.ImaFactory imaFactory; - @Nullable private final Uri adTagUri; - @Nullable private final String adsResponse; + @Nullable + private final Uri adTagUri; + @Nullable + private final String adsResponse; private final ImaSdkSettings imaSdkSettings; private final Timeline.Period period; private final Handler handler; @@ -497,45 +519,70 @@ public ImaAdsLoader build() { private final Runnable updateAdProgressRunnable; private final BiMap adInfoByAdMediaInfo; - private @MonotonicNonNull AdDisplayContainer adDisplayContainer; - private @MonotonicNonNull AdsLoader adsLoader; + private @MonotonicNonNull + AdDisplayContainer adDisplayContainer; + private @MonotonicNonNull + AdsLoader adsLoader; private boolean wasSetPlayerCalled; - @Nullable private Player nextPlayer; - @Nullable private Object pendingAdRequestContext; + @Nullable + private Player nextPlayer; + @Nullable + private Object pendingAdRequestContext; private List supportedMimeTypes; - @Nullable private EventListener eventListener; - @Nullable private Player player; + @Nullable + private EventListener eventListener; + @Nullable + private Player player; private DataSpec adTagDataSpec; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercent; - @Nullable private AdsManager adsManager; + @Nullable + private AdsManager adsManager; private boolean isAdsManagerInitialized; private boolean hasAdPlaybackState; - @Nullable private AdLoadException pendingAdLoadError; + @Nullable + private AdLoadException pendingAdLoadError; private Timeline timeline; private long contentDurationMs; private AdPlaybackState adPlaybackState; // Fields tracking IMA's state. - /** Whether IMA has sent an ad event to pause content since the last resume content event. */ + /** + * Whether IMA has sent an ad event to pause content since the last resume content event. + */ private boolean imaPausedContent; - /** The current ad playback state. */ - private @ImaAdState int imaAdState; - /** The current ad media info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. */ - @Nullable private AdMediaInfo imaAdMediaInfo; - /** The current ad info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. */ - @Nullable private AdInfo imaAdInfo; - /** Whether IMA has been notified that playback of content has finished. */ + /** + * The current ad playback state. + */ + private @ImaAdState + int imaAdState; + /** + * The current ad media info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. + */ + @Nullable + private AdMediaInfo imaAdMediaInfo; + /** + * The current ad info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. + */ + @Nullable + private AdInfo imaAdInfo; + /** + * Whether IMA has been notified that playback of content has finished. + */ private boolean sentContentComplete; // Fields tracking the player/loader state. - /** Whether the player is playing an ad. */ + /** + * Whether the player is playing an ad. + */ private boolean playingAd; - /** Whether the player is buffering an ad. */ + /** + * Whether the player is buffering an ad. + */ private boolean bufferingAd; /** * If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET} @@ -546,7 +593,8 @@ public ImaAdsLoader build() { * The ad info for a pending ad for which the media failed preparation, or {@code null} if no * pending ads have failed to prepare. */ - @Nullable private AdInfo pendingAdPrepareErrorAdInfo; + @Nullable + private AdInfo pendingAdPrepareErrorAdInfo; /** * If a content period has finished but IMA has not yet called {@link * ComponentListener#playAd(AdMediaInfo)}, stores the value of {@link @@ -559,7 +607,9 @@ public ImaAdsLoader build() { * content progress should increase. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressOffsetMs; - /** Stores the pending content position when a seek operation was intercepted to play an ad. */ + /** + * Stores the pending content position when a seek operation was intercepted to play an ad. + */ private long pendingContentPositionMs; /** * Whether {@link ComponentListener#getContentProgress()} has sent {@link @@ -577,13 +627,13 @@ public ImaAdsLoader build() { * *

If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead. * - * @param context The context. + * @param context The context. * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. * @deprecated Use {@link Builder} to create an instance. Pass the ad tag URI when setting media - * item playback properties (if using the media item API) or as a {@link DataSpec} when - * constructing the {@link AdsMediaSource} (if using media sources directly). + * item playback properties (if using the media item API) or as a {@link DataSpec} when + * constructing the {@link AdsMediaSource} (if using media sources directly). */ @Deprecated public ImaAdsLoader(Context context, Uri adTagUri) { @@ -672,10 +722,10 @@ public AdDisplayContainer getAdDisplayContainer() { * the player. * * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code - * null} if playing audio-only ads. + * null} if playing audio-only ads. * @deprecated Use {@link #requestAds(DataSpec, ViewGroup)}, specifying the ad tag data spec to - * request, and migrate off deprecated builder methods/constructor that require an ad tag or - * ads response. + * request, and migrate off deprecated builder methods/constructor that require an ad tag or ads + * response. */ @Deprecated public void requestAds(@Nullable ViewGroup adViewGroup) { @@ -690,9 +740,9 @@ public void requestAds(@Nullable ViewGroup adViewGroup) { * the player. * * @param adTagDataSpec The data specification of the ad tag to load. See class javadoc for - * information about compatible ad tag formats. - * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code - * null} if playing audio-only ads. + * information about compatible ad tag formats. + * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or + * {@code null} if playing audio-only ads. */ public void requestAds(DataSpec adTagDataSpec, @Nullable ViewGroup adViewGroup) { if (hasAdPlaybackState || adsManager != null || pendingAdRequestContext != null) { @@ -1529,8 +1579,8 @@ private void ensureSentContentCompleteIfAtEndOfStream() { && contentDurationMs != C.TIME_UNSET && pendingContentPositionMs == C.TIME_UNSET && getContentPeriodPositionMs(checkNotNull(player), timeline, period) - + THRESHOLD_END_OF_CONTENT_MS - >= contentDurationMs) { + + THRESHOLD_END_OF_CONTENT_MS + >= contentDurationMs) { sendContentComplete(); } } @@ -1634,8 +1684,8 @@ private static long getContentPeriodPositionMs( long contentWindowPositionMs = player.getContentPosition(); return contentWindowPositionMs - (timeline.isEmpty() - ? 0 - : timeline.getPeriod(/* periodIndex= */ 0, period).getPositionInWindowMs()); + ? 0 + : timeline.getPeriod(/* periodIndex= */ 0, period).getPositionInWindowMs()); } private static Looper getImaLooper() { @@ -1673,10 +1723,10 @@ private void destroyAdsManager() { private final class ComponentListener implements AdsLoadedListener, - ContentProgressProvider, - AdEventListener, - AdErrorListener, - VideoAdPlayer { + ContentProgressProvider, + AdEventListener, + AdErrorListener, + VideoAdPlayer { // AdsLoader.AdsLoadedListener implementation. @@ -1850,6 +1900,7 @@ public void release() { // TODO: Consider moving this into AdPlaybackState. private static final class AdInfo { + public final int adGroupIndex; public final int adIndexInAdGroup; @@ -1891,6 +1942,7 @@ public String toString() { * ImaSdkFactory}. */ private static final class DefaultImaFactory implements ImaUtil.ImaFactory { + @Override public ImaSdkSettings createImaSdkSettings() { return ImaSdkFactory.getInstance().createImaSdkSettings(); diff --git a/automatedtests/src/androidTestR2_12_1/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java b/automatedtests/src/androidTestR2_12_1/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java index 4077224e..cf073e95 100644 --- a/automatedtests/src/androidTestR2_12_1/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java +++ b/automatedtests/src/androidTestR2_12_1/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java @@ -18,9 +18,7 @@ import android.content.Context; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.Nullable; - import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; import com.google.ads.interactivemedia.v3.api.AdError; import com.google.ads.interactivemedia.v3.api.AdErrorEvent; @@ -41,48 +39,68 @@ import com.google.android.exoplayer2.upstream.DataSchemeDataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Util; - import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; -/** Utilities for working with IMA SDK and IMA extension data types. */ +/** + * Utilities for working with IMA SDK and IMA extension data types. + */ /* package */ final class ImaUtil { - /** Factory for objects provided by the IMA SDK. */ + /** + * Factory for objects provided by the IMA SDK. + */ public interface ImaFactory { - /** Creates {@link ImaSdkSettings} for configuring the IMA SDK. */ + + /** + * Creates {@link ImaSdkSettings} for configuring the IMA SDK. + */ ImaSdkSettings createImaSdkSettings(); + /** * Creates {@link AdsRenderingSettings} for giving the {@link AdsManager} parameters that * control rendering of ads. */ AdsRenderingSettings createAdsRenderingSettings(); + /** * Creates an {@link AdDisplayContainer} to hold the player for video ads, a container for * non-linear ads, and slots for companion ads. */ AdDisplayContainer createAdDisplayContainer(ViewGroup container, VideoAdPlayer player); - /** Creates an {@link AdDisplayContainer} to hold the player for audio ads. */ + + /** + * Creates an {@link AdDisplayContainer} to hold the player for audio ads. + */ AdDisplayContainer createAudioAdDisplayContainer(Context context, VideoAdPlayer player); + /** * Creates a {@link FriendlyObstruction} to describe an obstruction considered "friendly" for * viewability measurement purposes. */ FriendlyObstruction createFriendlyObstruction( - View view, - FriendlyObstructionPurpose friendlyObstructionPurpose, - @Nullable String reasonDetail); - /** Creates an {@link AdsRequest} to contain the data used to request ads. */ + View view, + FriendlyObstructionPurpose friendlyObstructionPurpose, + @Nullable String reasonDetail); + + /** + * Creates an {@link AdsRequest} to contain the data used to request ads. + */ AdsRequest createAdsRequest(); - /** Creates an {@link AdsLoader} for requesting ads using the specified settings. */ + + /** + * Creates an {@link AdsLoader} for requesting ads using the specified settings. + */ AdsLoader createAdsLoader( - Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer); + Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer); } - /** Stores configuration for ad loading and playback. */ + /** + * Stores configuration for ad loading and playback. + */ public static final class Configuration { public final long adPreloadTimeoutMs; @@ -91,13 +109,20 @@ public static final class Configuration { public final boolean focusSkipButtonWhenAvailable; public final boolean playAdBeforeStartPosition; public final int mediaBitrate; - @Nullable public final List adMediaMimeTypes; - @Nullable public final Set adUiElements; - @Nullable public final Collection companionAdSlots; - @Nullable public final AdErrorEvent.AdErrorListener applicationAdErrorListener; - @Nullable public final AdEvent.AdEventListener applicationAdEventListener; - @Nullable public final VideoAdPlayer.VideoAdPlayerCallback applicationVideoAdPlayerCallback; - @Nullable public final ImaSdkSettings imaSdkSettings; + @Nullable + public final List adMediaMimeTypes; + @Nullable + public final Set adUiElements; + @Nullable + public final Collection companionAdSlots; + @Nullable + public final AdErrorEvent.AdErrorListener applicationAdErrorListener; + @Nullable + public final AdEvent.AdEventListener applicationAdEventListener; + @Nullable + public final VideoAdPlayer.VideoAdPlayerCallback applicationVideoAdPlayerCallback; + @Nullable + public final ImaSdkSettings imaSdkSettings; public final boolean debugModeEnabled; public Configuration( @@ -179,7 +204,9 @@ public static AdPlaybackState getInitialAdPlaybackStateForCuePoints(List return new AdPlaybackState(adGroupTimesUs); } - /** Returns an {@link AdsRequest} based on the specified ad tag {@link DataSpec}. */ + /** + * Returns an {@link AdsRequest} based on the specified ad tag {@link DataSpec}. + */ public static AdsRequest getAdsRequestForAdTagDataSpec( ImaFactory imaFactory, DataSpec adTagDataSpec) throws IOException { AdsRequest request = imaFactory.createAdsRequest(); @@ -197,7 +224,9 @@ public static AdsRequest getAdsRequestForAdTagDataSpec( return request; } - /** Returns whether the ad error indicates that an entire ad group failed to load. */ + /** + * Returns whether the ad error indicates that an entire ad group failed to load. + */ public static boolean isAdGroupLoadError(AdError adError) { // TODO: Find out what other errors need to be handled (if any), and whether each one relates to // a single ad, ad group or the whole timeline. @@ -205,5 +234,6 @@ public static boolean isAdGroupLoadError(AdError adError) { || adError.getErrorCode() == AdError.AdErrorCode.UNKNOWN_ERROR; } - private ImaUtil() {} + private ImaUtil() { + } } diff --git a/automatedtests/src/androidTestR2_12_1/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java b/automatedtests/src/androidTestR2_12_1/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java index 13b14fdc..c5146172 100644 --- a/automatedtests/src/androidTestR2_12_1/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java +++ b/automatedtests/src/androidTestR2_12_1/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java @@ -5,17 +5,13 @@ import android.net.Uri; import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; -import android.view.ViewGroup; - import androidx.annotation.Nullable; - import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.PlaybackPreparer; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; @@ -40,160 +36,161 @@ public class SimplePlayerTestActivity extends SimplePlayerBaseActivity implements PlaybackPreparer { - MediaSourceFactory mediaSourceFactory; - - public void initExoPlayer() { - // Hopfully this will not channge the track selection set programmatically - TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory( - AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS * 10, - AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS * 10, - AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, - AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION - ); - DefaultTrackSelector.ParametersBuilder builder = - new DefaultTrackSelector.ParametersBuilder(/* context= */ this); - DefaultTrackSelector.Parameters trackSelectorParameters = builder - .build(); - - mediaSourceFactory = - new DefaultMediaSourceFactory( buildDataSourceFactory() ) - .setAdsLoaderProvider(this::getAdsLoader) - .setAdViewProvider(playerView); - - trackSelector = new DefaultTrackSelector(/* context= */ this, trackSelectionFactory); - trackSelector.setParameters(trackSelectorParameters); - RenderersFactory renderersFactory = new DefaultRenderersFactory(/* context= */ this); - player = - new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory) - .setMediaSourceFactory(mediaSourceFactory) - .setTrackSelector(trackSelector) - .build(); - playerView.setPlaybackPreparer( this ); + MediaSourceFactory mediaSourceFactory; + + public void initExoPlayer() { + // Hopfully this will not channge the track selection set programmatically + TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory( + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS * 10, + AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS * 10, + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, + AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION + ); + DefaultTrackSelector.ParametersBuilder builder = + new DefaultTrackSelector.ParametersBuilder(/* context= */ this); + DefaultTrackSelector.Parameters trackSelectorParameters = builder + .build(); + + mediaSourceFactory = + new DefaultMediaSourceFactory(buildDataSourceFactory()) + .setAdsLoaderProvider(this::getAdsLoader) + .setAdViewProvider(playerView); + + trackSelector = new DefaultTrackSelector(/* context= */ this, trackSelectionFactory); + trackSelector.setParameters(trackSelectorParameters); + RenderersFactory renderersFactory = new DefaultRenderersFactory(/* context= */ this); + player = + new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory) + .setMediaSourceFactory(mediaSourceFactory) + .setTrackSelector(trackSelector) + .build(); + playerView.setPlaybackPreparer(this); + } + + @Override + public void preparePlayback() { + player.prepare(); + } + + // This is for background playback, set appropriate notification and etc + public void initAudioSession() { + notificationManager = PlayerNotificationManager.createWithNotificationChannel( + getApplicationContext(), + PLAYBACK_CHANNEL_ID, + R.string.channel_name, + R.string.channel_description, + PLAYBACK_NOTIFICATION_ID, + new MDAdapter(), + new CustomNotificationListener() + ); + notificationManager.setUseNavigationActions(false); + notificationManager.setUseStopAction(true); + notificationManager.setPlayer(player); + + mediaSessionCompat = new MediaSessionCompat(this, "hello_world_media"); + notificationManager.setMediaSessionToken(mediaSessionCompat.getSessionToken()); + mediaSessionConnector = new MediaSessionConnector(mediaSessionCompat); + mediaSessionConnector.setPlayer(player); + } + + public DataSource.Factory buildDataSourceFactory() { + return new DefaultDataSourceFactory(this, "Android-automated_tests"); + } + + private MediaSource createMediaSource(Uri uri, String extension) { + MediaItem lMEdiaItem = createMediaItem(uri); + @C.ContentType int type = Util.inferContentType(uri, extension); + DataSource.Factory dataSourceFactory = buildDataSourceFactory(); + switch (type) { + case C.TYPE_DASH: + return new DashMediaSource.Factory(dataSourceFactory) + .createMediaSource(lMEdiaItem); + case C.TYPE_SS: + return new SsMediaSource.Factory(dataSourceFactory) + .createMediaSource(lMEdiaItem); + case C.TYPE_HLS: + return new HlsMediaSource.Factory(dataSourceFactory) + .createMediaSource(lMEdiaItem); + case C.TYPE_OTHER: + return new ProgressiveMediaSource.Factory(buildDataSourceFactory()) + .createMediaSource(lMEdiaItem); + default: + throw new IllegalStateException("Unsupported type: " + type); } - - @Override - public void preparePlayback() { - player.prepare(); + } + + public MediaSource createAdsMediaSource(MediaSource aMediaSource, Uri adTagUri) { + return new AdsMediaSource( + aMediaSource, + new DataSpec(loadedAdTagUri), + mediaSourceFactory, + getAdsLoader(loadedAdTagUri), + playerView); + } + + public MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) { + return createMediaSource(uri, overrideExtension); + } + + private MediaItem createMediaItem( + Uri uri) { + MediaItem.Builder lBuilder = + new MediaItem.Builder() + .setUri(uri); + return lBuilder.build(); + } + + + private AdsLoader getAdsLoader(Uri adTagUri) { + if (!adTagUri.equals(loadedAdTagUri)) { + releaseAdsLoader(); + loadedAdTagUri = adTagUri; } - - // This is for background playback, set appropriate notification and etc - public void initAudioSession() { - notificationManager = PlayerNotificationManager.createWithNotificationChannel( - getApplicationContext(), - PLAYBACK_CHANNEL_ID, - R.string.channel_name, - R.string.channel_description, - PLAYBACK_NOTIFICATION_ID, - new MDAdapter(), - new CustomNotificationListener() - ); - notificationManager.setUseNavigationActions(false); - notificationManager.setUseStopAction(true); - notificationManager.setPlayer(player); - - mediaSessionCompat = new MediaSessionCompat(this, "hello_world_media"); - notificationManager.setMediaSessionToken(mediaSessionCompat.getSessionToken()); - mediaSessionConnector = new MediaSessionConnector(mediaSessionCompat); - mediaSessionConnector.setPlayer(player); + // The ads loader is reused for multiple playbacks, so that ad playback can resume. + if (adsLoader == null) { + adsLoader = new ImaAdsLoader.Builder(/* context= */ this) + .setAdErrorListener(muxStats.getAdsImaSdkListener()) + .setAdEventListener(muxStats.getAdsImaSdkListener()) + .build(); } - - public DataSource.Factory buildDataSourceFactory() { - return new DefaultDataSourceFactory(this, "Android-automated_tests"); + adsLoader.setPlayer(player); + return adsLoader; + } + + private void releaseAdsLoader() { + if (adsLoader != null) { + adsLoader.release(); + adsLoader = null; + loadedAdTagUri = null; } - - private MediaSource createMediaSource( Uri uri, String extension) { - MediaItem lMEdiaItem = createMediaItem( uri ); - @C.ContentType int type = Util.inferContentType(uri, extension); - DataSource.Factory dataSourceFactory = buildDataSourceFactory(); - switch (type) { - case C.TYPE_DASH: - return new DashMediaSource.Factory (dataSourceFactory) - .createMediaSource(lMEdiaItem); - case C.TYPE_SS: - return new SsMediaSource.Factory(dataSourceFactory) - .createMediaSource(lMEdiaItem); - case C.TYPE_HLS: - return new HlsMediaSource.Factory(dataSourceFactory) - .createMediaSource(lMEdiaItem); - case C.TYPE_OTHER: - return new ProgressiveMediaSource.Factory( buildDataSourceFactory() ) - .createMediaSource(lMEdiaItem); - default: - throw new IllegalStateException("Unsupported type: " + type); - } + } + + @Override + public void startPlayback() { + Uri testUri = Uri.parse(urlToPlay); + testMediaSource = buildMediaSource(testUri, null); + if (loadedAdTagUri != null) { + testMediaSource = createAdsMediaSource(testMediaSource, loadedAdTagUri); } - public MediaSource createAdsMediaSource(MediaSource aMediaSource, Uri adTagUri) { - return new AdsMediaSource( - aMediaSource, - new DataSpec( loadedAdTagUri ), - mediaSourceFactory, - getAdsLoader( loadedAdTagUri ), - playerView ); - } + player.setPlayWhenReady(true); + player.setMediaSource(testMediaSource); + player.prepare(); + } - public MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) { - return createMediaSource( uri, overrideExtension ); - } - - private MediaItem createMediaItem( - Uri uri ) { - MediaItem.Builder lBuilder = - new MediaItem.Builder() - .setUri(uri); - return lBuilder.build(); - } - - - private AdsLoader getAdsLoader(Uri adTagUri) { - if (!adTagUri.equals(loadedAdTagUri)) { - releaseAdsLoader(); - loadedAdTagUri = adTagUri; - } - // The ads loader is reused for multiple playbacks, so that ad playback can resume. - if (adsLoader == null) { - adsLoader = new ImaAdsLoader.Builder(/* context= */ this) - .setAdErrorListener( muxStats.getAdsImaSdkListener() ) - .setAdEventListener( muxStats.getAdsImaSdkListener() ) - .build(); - } - adsLoader.setPlayer(player); - return adsLoader; - } - - private void releaseAdsLoader() { - if (adsLoader != null) { - adsLoader.release(); - adsLoader = null; - loadedAdTagUri = null; - } - } + class CustomNotificationListener implements PlayerNotificationManager.NotificationListener { @Override - public void startPlayback() { - Uri testUri = Uri.parse(urlToPlay); - testMediaSource = buildMediaSource(testUri, null); - if (loadedAdTagUri != null) { - testMediaSource = createAdsMediaSource(testMediaSource, loadedAdTagUri); - } - - player.setPlayWhenReady(true); - player.setMediaSource( testMediaSource ); - player.prepare( ); + public void onNotificationCancelled(int notificationId, boolean dismissedByUser) { + // TODO implement this + Log.e(TAG, "onNotificationCancelled"); } - class CustomNotificationListener implements PlayerNotificationManager.NotificationListener { - - @Override - public void onNotificationCancelled(int notificationId, boolean dismissedByUser) { - // TODO implement this - Log.e(TAG, "onNotificationCancelled"); - } - - @Override - public void onNotificationPosted(int notificationId, Notification notification, boolean ongoing) { - // TODO implement this - Log.e(TAG, "onNotificationPosted"); - } + @Override + public void onNotificationPosted(int notificationId, Notification notification, + boolean ongoing) { + // TODO implement this + Log.e(TAG, "onNotificationPosted"); } + } } diff --git a/automatedtests/src/androidTestR2_9_6/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java b/automatedtests/src/androidTestR2_9_6/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java index 66237627..7c8ca0c8 100644 --- a/automatedtests/src/androidTestR2_9_6/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java +++ b/automatedtests/src/androidTestR2_9_6/java/com/mux/stats/sdk/muxstats/automatedtests/ui/SimplePlayerTestActivity.java @@ -3,152 +3,145 @@ import android.net.Uri; import android.support.v4.media.session.MediaSessionCompat; - import androidx.annotation.Nullable; - import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; -import com.google.android.exoplayer2.offline.FilteringManifestParser; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource; -import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistParserFactory; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; -import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.RandomTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.ui.PlayerNotificationManager; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Util; import com.mux.stats.sdk.muxstats.automatedtests.R; - import java.lang.reflect.Constructor; public class SimplePlayerTestActivity extends SimplePlayerBaseActivity { - public void initExoPlayer() { - // Hopfully this will not channge the track selection set programmatically - TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory( - AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS * 10, - AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS * 10, - AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, - AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION - ); - DefaultTrackSelector.Parameters trackSelectorParameters - = new DefaultTrackSelector.ParametersBuilder().build(); - trackSelector = new DefaultTrackSelector(trackSelectionFactory); - trackSelector.setParameters(trackSelectorParameters); - RenderersFactory renderersFactory = new DefaultRenderersFactory(/* context= */ this); - player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector); - } - - public void initAudioSession() { - notificationManager = PlayerNotificationManager.createWithNotificationChannel( - getApplicationContext(), - PLAYBACK_CHANNEL_ID, - R.string.channel_name, - PLAYBACK_NOTIFICATION_ID, - new MDAdapter() - ); - - notificationManager.setUseNavigationActions(false); - notificationManager.setPlayer(player); - mediaSessionCompat = new MediaSessionCompat(this, "hello_world_media"); - notificationManager.setMediaSessionToken(mediaSessionCompat.getSessionToken()); - mediaSessionConnector = new MediaSessionConnector(mediaSessionCompat); - } - - public DataSource.Factory buildDataSourceFactory() { - return new DefaultDataSourceFactory(this, "Android-automated_tests"); + public void initExoPlayer() { + // Hopfully this will not channge the track selection set programmatically + TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory( + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS * 10, + AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS * 10, + AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, + AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION + ); + DefaultTrackSelector.Parameters trackSelectorParameters + = new DefaultTrackSelector.ParametersBuilder().build(); + trackSelector = new DefaultTrackSelector(trackSelectionFactory); + trackSelector.setParameters(trackSelectorParameters); + RenderersFactory renderersFactory = new DefaultRenderersFactory(/* context= */ this); + player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector); + } + + public void initAudioSession() { + notificationManager = PlayerNotificationManager.createWithNotificationChannel( + getApplicationContext(), + PLAYBACK_CHANNEL_ID, + R.string.channel_name, + PLAYBACK_NOTIFICATION_ID, + new MDAdapter() + ); + + notificationManager.setUseNavigationActions(false); + notificationManager.setPlayer(player); + mediaSessionCompat = new MediaSessionCompat(this, "hello_world_media"); + notificationManager.setMediaSessionToken(mediaSessionCompat.getSessionToken()); + mediaSessionConnector = new MediaSessionConnector(mediaSessionCompat); + } + + public DataSource.Factory buildDataSourceFactory() { + return new DefaultDataSourceFactory(this, "Android-automated_tests"); + } + + @SuppressWarnings("unchecked") + public MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) { + @C.ContentType int type = Util.inferContentType(uri, overrideExtension); + DataSource.Factory dataSourceFactory = buildDataSourceFactory(); + switch (type) { + case C.TYPE_DASH: + return new DashMediaSource.Factory(dataSourceFactory) + .createMediaSource(uri); + case C.TYPE_SS: + return new SsMediaSource.Factory(dataSourceFactory) + .createMediaSource(uri); + case C.TYPE_HLS: + return new HlsMediaSource.Factory(dataSourceFactory) + .createMediaSource(uri); + case C.TYPE_OTHER: + return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); + default: { + throw new IllegalStateException("Unsupported type: " + type); + } } - - @SuppressWarnings("unchecked") - public MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) { - @C.ContentType int type = Util.inferContentType(uri, overrideExtension); - DataSource.Factory dataSourceFactory = buildDataSourceFactory(); - switch (type) { - case C.TYPE_DASH: - return new DashMediaSource.Factory(dataSourceFactory) - .createMediaSource(uri); - case C.TYPE_SS: - return new SsMediaSource.Factory(dataSourceFactory) - .createMediaSource(uri); - case C.TYPE_HLS: - return new HlsMediaSource.Factory(dataSourceFactory) - .createMediaSource(uri); - case C.TYPE_OTHER: - return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); - default: { - throw new IllegalStateException("Unsupported type: " + type); + } + + /** + * Returns an ads media source, reusing the ads loader if one exists. + */ + public @Nullable + MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { + // Load the extension source using reflection so the demo app doesn't have to depend on it. + // The ads loader is reused for multiple playbacks, so that ad playback can resume. + try { + Class loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader"); + if (adsLoader == null) { + // Full class names used so the LINT.IfChange rule triggers should any of the classes move. + // LINT.IfChange + Constructor loaderConstructor = + loaderClass + .asSubclass(AdsLoader.class) + .getConstructor(android.content.Context.class, android.net.Uri.class); + // LINT.ThenChange(../../../../../../../../proguard-rules.txt) + adsLoader = loaderConstructor.newInstance(this, adTagUri); + } + adsLoader.setPlayer(player); + AdsMediaSource.MediaSourceFactory adMediaSourceFactory = + new AdsMediaSource.MediaSourceFactory() { + @Override + public MediaSource createMediaSource(Uri uri) { + return SimplePlayerTestActivity.this.buildMediaSource(uri, null); } - } - } - /** Returns an ads media source, reusing the ads loader if one exists. */ - public @Nullable - MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { - // Load the extension source using reflection so the demo app doesn't have to depend on it. - // The ads loader is reused for multiple playbacks, so that ad playback can resume. - try { - Class loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader"); - if (adsLoader == null) { - // Full class names used so the LINT.IfChange rule triggers should any of the classes move. - // LINT.IfChange - Constructor loaderConstructor = - loaderClass - .asSubclass(AdsLoader.class) - .getConstructor(android.content.Context.class, android.net.Uri.class); - // LINT.ThenChange(../../../../../../../../proguard-rules.txt) - adsLoader = loaderConstructor.newInstance(this, adTagUri); + @Override + public int[] getSupportedTypes() { + return new int[]{C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; } - adsLoader.setPlayer(player); - AdsMediaSource.MediaSourceFactory adMediaSourceFactory = - new AdsMediaSource.MediaSourceFactory() { - @Override - public MediaSource createMediaSource(Uri uri) { - return SimplePlayerTestActivity.this.buildMediaSource(uri, null); - } - - @Override - public int[] getSupportedTypes() { - return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; - } - }; - - // Because of how this is loaded via reflection, we know that this will be - // a ImaAdsLoader, so cast it over so that we can get a reference to the - // real IMA AdsLoader instance. - muxStats.monitorImaAdsLoader(((ImaAdsLoader) adsLoader).getAdsLoader()); - return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView); - } catch (ClassNotFoundException e) { - // IMA extension not loaded. - return null; - } catch (Exception e) { - throw new RuntimeException(e); - } + }; + + // Because of how this is loaded via reflection, we know that this will be + // a ImaAdsLoader, so cast it over so that we can get a reference to the + // real IMA AdsLoader instance. + muxStats.monitorImaAdsLoader(((ImaAdsLoader) adsLoader).getAdsLoader()); + return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView); + } catch (ClassNotFoundException e) { + // IMA extension not loaded. + return null; + } catch (Exception e) { + throw new RuntimeException(e); } - - @Override - public void startPlayback() { - Uri testUri = Uri.parse(urlToPlay); - testMediaSource = buildMediaSource(testUri, null); - if (loadedAdTagUri != null) { - testMediaSource = createAdsMediaSource(testMediaSource, loadedAdTagUri); - } - - player.setPlayWhenReady(true); - player.prepare( testMediaSource ); + } + + @Override + public void startPlayback() { + Uri testUri = Uri.parse(urlToPlay); + testMediaSource = buildMediaSource(testUri, null); + if (loadedAdTagUri != null) { + testMediaSource = createAdsMediaSource(testMediaSource, loadedAdTagUri); } + + player.setPlayWhenReady(true); + player.prepare(testMediaSource); + } } diff --git a/automatedtests/src/androidTestR2_9_6/java/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/automatedtests/src/androidTestR2_9_6/java/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index d9676d8c..8f4afaf8 100644 --- a/automatedtests/src/androidTestR2_9_6/java/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/automatedtests/src/androidTestR2_9_6/java/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -80,31 +80,36 @@ * #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling * {@link #release()}. * - *

The IMA SDK can take into account video control overlay views when calculating ad viewability. - * For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} and {@link - * AdViewProvider#getAdOverlayViews()}. + *

The IMA SDK can take into account video control overlay views when calculating ad + * viewability. For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} + * and {@link AdViewProvider#getAdOverlayViews()}. */ public final class ImaAdsLoader implements Player.EventListener, - AdsLoader, - VideoAdPlayer, - ContentProgressProvider, - AdErrorListener, - AdsLoadedListener, - AdEventListener { + AdsLoader, + VideoAdPlayer, + ContentProgressProvider, + AdErrorListener, + AdsLoadedListener, + AdEventListener { static { ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); } - /** Builder for {@link ImaAdsLoader}. */ + /** + * Builder for {@link ImaAdsLoader}. + */ public static final class Builder { private final Context context; - @Nullable private ImaSdkSettings imaSdkSettings; - @Nullable private AdEventListener adEventListener; - @Nullable private Set adUiElements; + @Nullable + private ImaSdkSettings imaSdkSettings; + @Nullable + private AdEventListener adEventListener; + @Nullable + private Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; private int mediaBitrate; @@ -140,8 +145,7 @@ public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { } /** - * Sets a listener for ad events that will be passed to {@link - * AdsManager#addAdEventListener(AdEventListener)}. + * Sets a listener for ad events that will be passed to {@link AdsManager#addAdEventListener(AdEventListener)}. * * @param adEventListener The ad event listener. * @return This builder, for convenience. @@ -207,7 +211,7 @@ public Builder setMaxMediaBitrate(int bitrate) { * setting is {@code true}. * * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on - * Android TV devices. + * Android TV devices. * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ @@ -225,9 +229,8 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila /** * Returns a new {@link ImaAdsLoader} for the specified ad tag. * - * @param adTagUri The URI of a compatible ad tag to load. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * information on compatible ad tags. + * @param adTagUri The URI of a compatible ad tag to load. See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for information on compatible ad tags. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdTag(Uri adTagUri) { @@ -249,7 +252,7 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) { * Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response. * * @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of - * making a request via an ad tag URL. + * making a request via an ad tag URL. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdsResponse(String adsResponse) { @@ -279,7 +282,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private static final String IMA_SDK_SETTINGS_PLAYER_TYPE = "google/exo.ext.ima"; private static final String IMA_SDK_SETTINGS_PLAYER_VERSION = ExoPlayerLibraryInfo.VERSION; - /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ + /** + * The value used in {@link VideoProgressUpdate}s to indicate an unset duration. + */ private static final long IMA_DURATION_UNSET = -1L; /** @@ -288,17 +293,24 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final long END_OF_CONTENT_POSITION_THRESHOLD_MS = 5000; - /** The maximum duration before an ad break that IMA may start preloading the next ad. */ + /** + * The maximum duration before an ad break that IMA may start preloading the next ad. + */ private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000; private static final int TIMEOUT_UNSET = -1; private static final int BITRATE_UNSET = -1; - /** The state of ad playback. */ + /** + * The state of ad playback. + */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) - private @interface ImaAdState {} + private @interface ImaAdState { + + } + /** * The ad playback state when IMA is not playing an ad. */ @@ -312,25 +324,32 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final int IMA_AD_STATE_PAUSED = 2; - private final @Nullable Uri adTagUri; - private final @Nullable String adsResponse; + private final @Nullable + Uri adTagUri; + private final @Nullable + String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; private final boolean focusSkipButtonWhenAvailable; private final int mediaBitrate; - private final @Nullable Set adUiElements; - private final @Nullable AdEventListener adEventListener; + private final @Nullable + Set adUiElements; + private final @Nullable + AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; private final List adCallbacks; private final AdDisplayContainer adDisplayContainer; private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader; - @Nullable private Player nextPlayer; + @Nullable + private Player nextPlayer; private Object pendingAdRequestContext; private List supportedMimeTypes; - @Nullable private EventListener eventListener; - @Nullable private Player player; + @Nullable + private EventListener eventListener; + @Nullable + private Player player; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercentage; @@ -344,14 +363,23 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking IMA's state. - /** The expected ad group index that IMA should load next. */ + /** + * The expected ad group index that IMA should load next. + */ private int expectedAdGroupIndex; - /** The index of the current ad group that IMA is loading. */ + /** + * The index of the current ad group that IMA is loading. + */ private int adGroupIndex; - /** Whether IMA has sent an ad event to pause content since the last resume content event. */ + /** + * Whether IMA has sent an ad event to pause content since the last resume content event. + */ private boolean imaPausedContent; - /** The current ad playback state. */ - private @ImaAdState int imaAdState; + /** + * The current ad playback state. + */ + private @ImaAdState + int imaAdState; /** * Whether {@link com.google.ads.interactivemedia.v3.api.AdsLoader#contentComplete()} has been * called since starting ad playback. @@ -360,7 +388,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking the player/loader state. - /** Whether the player is playing an ad. */ + /** + * Whether the player is playing an ad. + */ private boolean playingAd; /** * If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET} @@ -383,9 +413,13 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * content progress should increase. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressOffsetMs; - /** Stores the pending content position when a seek operation was intercepted to play an ad. */ + /** + * Stores the pending content position when a seek operation was intercepted to play an ad. + */ private long pendingContentPositionMs; - /** Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. */ + /** + * Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. + */ private boolean sentPendingContentPositionMs; /** @@ -393,10 +427,10 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * *

If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead. * - * @param context The context. + * @param context The context. * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. */ public ImaAdsLoader(Context context, Uri adTagUri) { this( @@ -416,12 +450,13 @@ public ImaAdsLoader(Context context, Uri adTagUri) { /** * Creates a new IMA ads loader. * - * @param context The context. - * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * @param context The context. + * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. * @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to - * use the default settings. If set, the player type and version fields may be overwritten. + * use the default settings. If set, the player type and version fields may + * be overwritten. * @deprecated Use {@link ImaAdsLoader.Builder}. */ @Deprecated @@ -485,8 +520,8 @@ private ImaAdsLoader( } /** - * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by - * this instance. + * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by this + * instance. */ public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() { return adsLoader; @@ -1350,7 +1385,7 @@ private void maybeNotifyInternalError(String name, Exception cause) { private static long[] getAdGroupTimesUs(List cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. - return new long[] {0}; + return new long[]{0}; } int count = cuePoints.size(); @@ -1388,24 +1423,44 @@ private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) { } } - /** Factory for objects provided by the IMA SDK. */ + /** + * Factory for objects provided by the IMA SDK. + */ // @VisibleForTesting /* package */ interface ImaFactory { - /** @see ImaSdkSettings */ + + /** + * @see ImaSdkSettings + */ ImaSdkSettings createImaSdkSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() + */ AdsRenderingSettings createAdsRenderingSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() + */ AdDisplayContainer createAdDisplayContainer(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() + */ AdsRequest createAdsRequest(); - /** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */ + + /** + * @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) + */ com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer); } - /** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */ + /** + * Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. + */ private static final class DefaultImaFactory implements ImaFactory { + @Override public ImaSdkSettings createImaSdkSettings() { return ImaSdkFactory.getInstance().createImaSdkSettings(); diff --git a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DemoApplication.java index 6985d42b..3e3d4fd3 100644 --- a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -64,19 +64,25 @@ public void onCreate() { userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); } - /** Returns a {@link DataSource.Factory}. */ + /** + * Returns a {@link DataSource.Factory}. + */ public DataSource.Factory buildDataSourceFactory() { DefaultDataSourceFactory upstreamFactory = new DefaultDataSourceFactory(this, buildHttpDataSourceFactory()); return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache()); } - /** Returns a {@link HttpDataSource.Factory}. */ + /** + * Returns a {@link HttpDataSource.Factory}. + */ public HttpDataSource.Factory buildHttpDataSourceFactory() { return new DefaultHttpDataSourceFactory(userAgent); } - /** Returns whether extension renderers should be used. */ + /** + * Returns whether extension renderers should be used. + */ public boolean useExtensionRenderers() { return "withExtensions".equals(BuildConfig.FLAVOR); } @@ -86,8 +92,8 @@ public RenderersFactory buildRenderersFactory(boolean preferExtensionRenderer) { int extensionRendererMode = useExtensionRenderers() ? (preferExtensionRenderer - ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) + ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; return new DefaultRenderersFactory(/* context= */ this) .setExtensionRendererMode(extensionRendererMode); diff --git a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index c3909dfe..50ff317d 100644 --- a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -25,7 +25,9 @@ import com.google.android.exoplayer2.util.Util; import java.util.List; -/** A service for downloading media. */ +/** + * A service for downloading media. + */ public class DemoDownloadService extends DownloadService { private static final String CHANNEL_ID = "download_channel"; diff --git a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DownloadTracker.java index 8baa52fb..9b080c3f 100644 --- a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -18,9 +18,9 @@ import android.content.Context; import android.content.DialogInterface; import android.net.Uri; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentManager; -import android.widget.Toast; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.offline.Download; @@ -37,18 +37,23 @@ import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; -/** Tracks media that has been downloaded. */ +/** + * Tracks media that has been downloaded. + */ public class DownloadTracker { - /** Listens for changes in the tracked downloads. */ + /** + * Listens for changes in the tracked downloads. + */ public interface Listener { - /** Called when the tracked downloads changed. */ + /** + * Called when the tracked downloads changed. + */ void onDownloadsChanged(); } @@ -60,7 +65,8 @@ public interface Listener { private final HashMap downloads; private final DownloadIndex downloadIndex; - @Nullable private StartDownloadDialogHelper startDownloadDialogHelper; + @Nullable + private StartDownloadDialogHelper startDownloadDialogHelper; public DownloadTracker( Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) { @@ -180,8 +186,8 @@ public void onDownloadRemoved(DownloadManager downloadManager, Download download private final class StartDownloadDialogHelper implements DownloadHelper.Callback, - DialogInterface.OnClickListener, - DialogInterface.OnDismissListener { + DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { private final FragmentManager fragmentManager; private final DownloadHelper downloadHelper; @@ -237,7 +243,7 @@ public void onPrepared(DownloadHelper helper) { @Override public void onPrepareError(DownloadHelper helper, IOException e) { Toast.makeText( - context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG) + context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG) .show(); Log.e(TAG, "Failed to start download", e); } diff --git a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 84f35d47..4cd37142 100644 --- a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -21,9 +21,6 @@ import android.graphics.Point; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; import android.util.Pair; import android.view.KeyEvent; import android.view.View; @@ -32,6 +29,9 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C.ContentType; import com.google.android.exoplayer2.ExoPlaybackException; @@ -78,14 +78,15 @@ import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; - import java.lang.reflect.Constructor; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; import java.util.UUID; -/** An activity that plays media using {@link SimpleExoPlayer}. */ +/** + * An activity that plays media using {@link SimpleExoPlayer}. + */ public class PlayerActivity extends AppCompatActivity implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener { @@ -125,6 +126,7 @@ public class PlayerActivity extends AppCompatActivity private static final String KEY_AUTO_PLAY = "auto_play"; private static final CookieManager DEFAULT_COOKIE_MANAGER; + static { DEFAULT_COOKIE_MANAGER = new CookieManager(); DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); @@ -354,8 +356,8 @@ private void initializePlayer() { Uri[] uris; String[] extensions; if (ACTION_VIEW.equals(action)) { - uris = new Uri[] {intent.getData()}; - extensions = new String[] {intent.getStringExtra(EXTENSION_EXTRA)}; + uris = new Uri[]{intent.getData()}; + extensions = new String[]{intent.getStringExtra(EXTENSION_EXTRA)}; } else if (ACTION_VIEW_LIST.equals(action)) { String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA); uris = new Uri[uriStrings.length]; @@ -450,7 +452,7 @@ private void initializePlayer() { CustomerVideoData customerVideoData = new CustomerVideoData(); customerVideoData.setVideoTitle(intent.getStringExtra(VIDEO_TITLE_EXTRA)); muxStats = new MuxStatsExoPlayer( - this, player, "demo-player", customerPlayerData, customerVideoData); + this, player, "demo-player", customerPlayerData, customerVideoData); Point size = new Point(); getWindowManager().getDefaultDisplay().getSize(size); muxStats.setScreenSize(size.x, size.y); @@ -586,13 +588,18 @@ private void clearStartPosition() { startPosition = C.TIME_UNSET; } - /** Returns a new DataSource factory. */ + /** + * Returns a new DataSource factory. + */ private DataSource.Factory buildDataSourceFactory() { return ((DemoApplication) getApplication()).buildDataSourceFactory(); } - /** Returns an ads media source, reusing the ads loader if one exists. */ - private @Nullable MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { + /** + * Returns an ads media source, reusing the ads loader if one exists. + */ + private @Nullable + MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { // Load the extension source using reflection so the demo app doesn't have to depend on it. // The ads loader is reused for multiple playbacks, so that ad playback can resume. try { @@ -617,7 +624,7 @@ public MediaSource createMediaSource(Uri uri) { @Override public int[] getSupportedTypes() { - return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; + return new int[]{C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; } }; return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView); diff --git a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index 7245de01..07bf356a 100644 --- a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -21,8 +21,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; import android.util.JsonReader; import android.view.Menu; import android.view.MenuInflater; @@ -36,6 +34,8 @@ import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.offline.DownloadService; @@ -49,12 +49,15 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -/** An activity for selecting from a list of media samples. */ +/** + * An activity for selecting from a list of media samples. + */ public class SampleChooserActivity extends AppCompatActivity implements DownloadTracker.Listener, OnChildClickListener { @@ -79,7 +82,7 @@ public void onCreate(Bundle savedInstanceState) { String dataUri = intent.getDataString(); String[] uris; if (dataUri != null) { - uris = new String[] {dataUri}; + uris = new String[]{dataUri}; } else { ArrayList uriList = new ArrayList<>(); AssetManager assetManager = getAssets(); @@ -228,7 +231,8 @@ protected List doInBackground(String... uris) { DataSpec dataSpec = new DataSpec(Uri.parse(uri)); InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); try { - readSampleGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result); + readSampleGroups( + new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)), result); } catch (Exception e) { Log.e(TAG, "Error loading sample list: " + uri, e); sawError = true; @@ -499,6 +503,7 @@ public SampleGroup(String title) { } private static final class DrmInfo { + public final String drmScheme; public final String drmLicenseUrl; public final String[] drmKeyRequestProperties; @@ -525,6 +530,7 @@ public void updateIntent(Intent intent) { } private abstract static class Sample { + public final String name; public final DrmInfo drmInfo; diff --git a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java index bc409410..d1b49e77 100644 --- a/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java +++ b/demo/src/r2_10_6/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java @@ -19,19 +19,18 @@ import android.content.DialogInterface; import android.content.res.Resources; import android.os.Bundle; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; import androidx.annotation.Nullable; -import com.google.android.material.tabs.TabLayout; +import androidx.appcompat.app.AppCompatDialog; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; -import androidx.appcompat.app.AppCompatDialog; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -39,11 +38,14 @@ import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.ui.TrackSelectionView; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.material.tabs.TabLayout; import java.util.ArrayList; import java.util.Collections; import java.util.List; -/** Dialog to select tracks. */ +/** + * Dialog to select tracks. + */ public final class TrackSelectionDialog extends DialogFragment { private final SparseArray tabFragments; @@ -79,9 +81,9 @@ public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) { * Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be * automatically updated when tracks are selected. * - * @param trackSelector The {@link DefaultTrackSelector}. + * @param trackSelector The {@link DefaultTrackSelector}. * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is - * dismissed. + * dismissed. */ public static TrackSelectionDialog createForTrackSelector( DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) { @@ -121,16 +123,17 @@ public static TrackSelectionDialog createForTrackSelector( /** * Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}. * - * @param titleId The resource id of the dialog title. - * @param mappedTrackInfo The {@link MappedTrackInfo} to display. - * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial - * track selection. + * @param titleId The resource id of the dialog title. + * @param mappedTrackInfo The {@link MappedTrackInfo} to display. + * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the + * initial track selection. * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track) - * can be made. - * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. - * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected. - * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is - * dismissed. + * can be made. + * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. + * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are + * selected. + * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog + * is dismissed. */ public static TrackSelectionDialog createForMappedTrackInfoAndParameters( int titleId, @@ -306,7 +309,9 @@ public CharSequence getPageTitle(int position) { } } - /** Fragment to show a track selection in tab of the track selection dialog. */ + /** + * Fragment to show a track selection in tab of the track selection dialog. + */ public static final class TrackSelectionViewFragment extends Fragment implements TrackSelectionView.TrackSelectionListener { diff --git a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java index 6985d42b..3e3d4fd3 100644 --- a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -64,19 +64,25 @@ public void onCreate() { userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); } - /** Returns a {@link DataSource.Factory}. */ + /** + * Returns a {@link DataSource.Factory}. + */ public DataSource.Factory buildDataSourceFactory() { DefaultDataSourceFactory upstreamFactory = new DefaultDataSourceFactory(this, buildHttpDataSourceFactory()); return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache()); } - /** Returns a {@link HttpDataSource.Factory}. */ + /** + * Returns a {@link HttpDataSource.Factory}. + */ public HttpDataSource.Factory buildHttpDataSourceFactory() { return new DefaultHttpDataSourceFactory(userAgent); } - /** Returns whether extension renderers should be used. */ + /** + * Returns whether extension renderers should be used. + */ public boolean useExtensionRenderers() { return "withExtensions".equals(BuildConfig.FLAVOR); } @@ -86,8 +92,8 @@ public RenderersFactory buildRenderersFactory(boolean preferExtensionRenderer) { int extensionRendererMode = useExtensionRenderers() ? (preferExtensionRenderer - ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) + ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; return new DefaultRenderersFactory(/* context= */ this) .setExtensionRendererMode(extensionRendererMode); diff --git a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index c3909dfe..50ff317d 100644 --- a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -25,7 +25,9 @@ import com.google.android.exoplayer2.util.Util; import java.util.List; -/** A service for downloading media. */ +/** + * A service for downloading media. + */ public class DemoDownloadService extends DownloadService { private static final String CHANNEL_ID = "download_channel"; diff --git a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java index a913a9b8..acb99f7b 100644 --- a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -18,9 +18,9 @@ import android.content.Context; import android.content.DialogInterface; import android.net.Uri; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentManager; -import android.widget.Toast; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.offline.Download; @@ -38,13 +38,19 @@ import java.util.HashMap; import java.util.concurrent.CopyOnWriteArraySet; -/** Tracks media that has been downloaded. */ +/** + * Tracks media that has been downloaded. + */ public class DownloadTracker { - /** Listens for changes in the tracked downloads. */ + /** + * Listens for changes in the tracked downloads. + */ public interface Listener { - /** Called when the tracked downloads changed. */ + /** + * Called when the tracked downloads changed. + */ void onDownloadsChanged(); } @@ -56,7 +62,8 @@ public interface Listener { private final HashMap downloads; private final DownloadIndex downloadIndex; - @Nullable private StartDownloadDialogHelper startDownloadDialogHelper; + @Nullable + private StartDownloadDialogHelper startDownloadDialogHelper; public DownloadTracker( Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) { @@ -157,8 +164,8 @@ public void onDownloadRemoved(DownloadManager downloadManager, Download download private final class StartDownloadDialogHelper implements DownloadHelper.Callback, - DialogInterface.OnClickListener, - DialogInterface.OnDismissListener { + DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { private final FragmentManager fragmentManager; private final DownloadHelper downloadHelper; @@ -214,7 +221,7 @@ public void onPrepared(DownloadHelper helper) { @Override public void onPrepareError(DownloadHelper helper, IOException e) { Toast.makeText( - context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG) + context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG) .show(); Log.e(TAG, "Failed to start download", e); } diff --git a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 078467eb..feeb6103 100644 --- a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -21,9 +21,6 @@ import android.graphics.Point; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; import android.util.Pair; import android.view.KeyEvent; import android.view.View; @@ -32,6 +29,9 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C.ContentType; import com.google.android.exoplayer2.ExoPlaybackException; @@ -79,14 +79,15 @@ import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; - import java.lang.reflect.Constructor; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; import java.util.UUID; -/** An activity that plays media using {@link SimpleExoPlayer}. */ +/** + * An activity that plays media using {@link SimpleExoPlayer}. + */ public class PlayerActivity extends AppCompatActivity implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener { @@ -126,6 +127,7 @@ public class PlayerActivity extends AppCompatActivity private static final String KEY_AUTO_PLAY = "auto_play"; private static final CookieManager DEFAULT_COOKIE_MANAGER; + static { DEFAULT_COOKIE_MANAGER = new CookieManager(); DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); @@ -356,8 +358,8 @@ private void initializePlayer() { Uri[] uris; String[] extensions; if (ACTION_VIEW.equals(action)) { - uris = new Uri[] {intent.getData()}; - extensions = new String[] {intent.getStringExtra(EXTENSION_EXTRA)}; + uris = new Uri[]{intent.getData()}; + extensions = new String[]{intent.getStringExtra(EXTENSION_EXTRA)}; } else if (ACTION_VIEW_LIST.equals(action)) { String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA); uris = new Uri[uriStrings.length]; @@ -452,7 +454,7 @@ private void initializePlayer() { CustomerVideoData customerVideoData = new CustomerVideoData(); customerVideoData.setVideoTitle(intent.getStringExtra(VIDEO_TITLE_EXTRA)); muxStats = new MuxStatsExoPlayer( - this, player, "demo-player", customerPlayerData, customerVideoData); + this, player, "demo-player", customerPlayerData, customerVideoData); Point size = new Point(); getWindowManager().getDefaultDisplay().getSize(size); muxStats.setScreenSize(size.x, size.y); @@ -588,13 +590,18 @@ private void clearStartPosition() { startPosition = C.TIME_UNSET; } - /** Returns a new DataSource factory. */ + /** + * Returns a new DataSource factory. + */ private DataSource.Factory buildDataSourceFactory() { return ((DemoApplication) getApplication()).buildDataSourceFactory(); } - /** Returns an ads media source, reusing the ads loader if one exists. */ - private @Nullable MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { + /** + * Returns an ads media source, reusing the ads loader if one exists. + */ + private @Nullable + MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { // Load the extension source using reflection so the demo app doesn't have to depend on it. // The ads loader is reused for multiple playbacks, so that ad playback can resume. try { @@ -619,7 +626,7 @@ public MediaSource createMediaSource(Uri uri) { @Override public int[] getSupportedTypes() { - return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; + return new int[]{C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; } }; diff --git a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index 7245de01..c2197101 100644 --- a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -21,8 +21,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; import android.util.JsonReader; import android.view.Menu; import android.view.MenuInflater; @@ -36,6 +34,8 @@ import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.offline.DownloadService; @@ -54,7 +54,9 @@ import java.util.Collections; import java.util.List; -/** An activity for selecting from a list of media samples. */ +/** + * An activity for selecting from a list of media samples. + */ public class SampleChooserActivity extends AppCompatActivity implements DownloadTracker.Listener, OnChildClickListener { @@ -79,7 +81,7 @@ public void onCreate(Bundle savedInstanceState) { String dataUri = intent.getDataString(); String[] uris; if (dataUri != null) { - uris = new String[] {dataUri}; + uris = new String[]{dataUri}; } else { ArrayList uriList = new ArrayList<>(); AssetManager assetManager = getAssets(); @@ -499,6 +501,7 @@ public SampleGroup(String title) { } private static final class DrmInfo { + public final String drmScheme; public final String drmLicenseUrl; public final String[] drmKeyRequestProperties; @@ -525,6 +528,7 @@ public void updateIntent(Intent intent) { } private abstract static class Sample { + public final String name; public final DrmInfo drmInfo; diff --git a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java index bc409410..d1b49e77 100644 --- a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java +++ b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java @@ -19,19 +19,18 @@ import android.content.DialogInterface; import android.content.res.Resources; import android.os.Bundle; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; import androidx.annotation.Nullable; -import com.google.android.material.tabs.TabLayout; +import androidx.appcompat.app.AppCompatDialog; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; -import androidx.appcompat.app.AppCompatDialog; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -39,11 +38,14 @@ import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.ui.TrackSelectionView; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.material.tabs.TabLayout; import java.util.ArrayList; import java.util.Collections; import java.util.List; -/** Dialog to select tracks. */ +/** + * Dialog to select tracks. + */ public final class TrackSelectionDialog extends DialogFragment { private final SparseArray tabFragments; @@ -79,9 +81,9 @@ public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) { * Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be * automatically updated when tracks are selected. * - * @param trackSelector The {@link DefaultTrackSelector}. + * @param trackSelector The {@link DefaultTrackSelector}. * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is - * dismissed. + * dismissed. */ public static TrackSelectionDialog createForTrackSelector( DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) { @@ -121,16 +123,17 @@ public static TrackSelectionDialog createForTrackSelector( /** * Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}. * - * @param titleId The resource id of the dialog title. - * @param mappedTrackInfo The {@link MappedTrackInfo} to display. - * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial - * track selection. + * @param titleId The resource id of the dialog title. + * @param mappedTrackInfo The {@link MappedTrackInfo} to display. + * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the + * initial track selection. * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track) - * can be made. - * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. - * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected. - * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is - * dismissed. + * can be made. + * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. + * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are + * selected. + * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog + * is dismissed. */ public static TrackSelectionDialog createForMappedTrackInfoAndParameters( int titleId, @@ -306,7 +309,9 @@ public CharSequence getPageTitle(int position) { } } - /** Fragment to show a track selection in tab of the track selection dialog. */ + /** + * Fragment to show a track selection in tab of the track selection dialog. + */ public static final class TrackSelectionViewFragment extends Fragment implements TrackSelectionView.TrackSelectionListener { diff --git a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 335f8374..e2b463c8 100644 --- a/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/demo/src/r2_10_6_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -19,11 +19,11 @@ import android.net.Uri; import android.os.Looper; import android.os.SystemClock; +import android.view.View; +import android.view.ViewGroup; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import android.view.View; -import android.view.ViewGroup; import com.google.ads.interactivemedia.v3.api.Ad; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; import com.google.ads.interactivemedia.v3.api.AdError; @@ -81,31 +81,36 @@ * #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling * {@link #release()}. * - *

The IMA SDK can take into account video control overlay views when calculating ad viewability. - * For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} and {@link - * AdViewProvider#getAdOverlayViews()}. + *

The IMA SDK can take into account video control overlay views when calculating ad + * viewability. For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} + * and {@link AdViewProvider#getAdOverlayViews()}. */ public final class ImaAdsLoader implements Player.EventListener, - AdsLoader, - VideoAdPlayer, - ContentProgressProvider, - AdErrorListener, - AdsLoadedListener, - AdEventListener { + AdsLoader, + VideoAdPlayer, + ContentProgressProvider, + AdErrorListener, + AdsLoadedListener, + AdEventListener { static { ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); } - /** Builder for {@link ImaAdsLoader}. */ + /** + * Builder for {@link ImaAdsLoader}. + */ public static final class Builder { private final Context context; - @Nullable private ImaSdkSettings imaSdkSettings; - @Nullable private AdEventListener adEventListener; - @Nullable private Set adUiElements; + @Nullable + private ImaSdkSettings imaSdkSettings; + @Nullable + private AdEventListener adEventListener; + @Nullable + private Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; private int mediaBitrate; @@ -141,8 +146,7 @@ public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { } /** - * Sets a listener for ad events that will be passed to {@link - * AdsManager#addAdEventListener(AdEventListener)}. + * Sets a listener for ad events that will be passed to {@link AdsManager#addAdEventListener(AdEventListener)}. * * @param adEventListener The ad event listener. * @return This builder, for convenience. @@ -208,7 +212,7 @@ public Builder setMaxMediaBitrate(int bitrate) { * setting is {@code true}. * * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on - * Android TV devices. + * Android TV devices. * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ @@ -218,7 +222,7 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila } @VisibleForTesting - /* package */ Builder setImaFactory(ImaFactory imaFactory) { + /* package */ Builder setImaFactory(ImaFactory imaFactory) { this.imaFactory = Assertions.checkNotNull(imaFactory); return this; } @@ -226,9 +230,8 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila /** * Returns a new {@link ImaAdsLoader} for the specified ad tag. * - * @param adTagUri The URI of a compatible ad tag to load. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * information on compatible ad tags. + * @param adTagUri The URI of a compatible ad tag to load. See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for information on compatible ad tags. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdTag(Uri adTagUri) { @@ -250,7 +253,7 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) { * Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response. * * @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of - * making a request via an ad tag URL. + * making a request via an ad tag URL. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdsResponse(String adsResponse) { @@ -280,7 +283,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private static final String IMA_SDK_SETTINGS_PLAYER_TYPE = "google/exo.ext.ima"; private static final String IMA_SDK_SETTINGS_PLAYER_VERSION = ExoPlayerLibraryInfo.VERSION; - /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ + /** + * The value used in {@link VideoProgressUpdate}s to indicate an unset duration. + */ private static final long IMA_DURATION_UNSET = -1L; /** @@ -289,17 +294,24 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final long END_OF_CONTENT_POSITION_THRESHOLD_MS = 5000; - /** The maximum duration before an ad break that IMA may start preloading the next ad. */ + /** + * The maximum duration before an ad break that IMA may start preloading the next ad. + */ private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000; private static final int TIMEOUT_UNSET = -1; private static final int BITRATE_UNSET = -1; - /** The state of ad playback. */ + /** + * The state of ad playback. + */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) - private @interface ImaAdState {} + private @interface ImaAdState { + + } + /** * The ad playback state when IMA is not playing an ad. */ @@ -313,14 +325,18 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final int IMA_AD_STATE_PAUSED = 2; - private final @Nullable Uri adTagUri; - private final @Nullable String adsResponse; + private final @Nullable + Uri adTagUri; + private final @Nullable + String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; private final boolean focusSkipButtonWhenAvailable; private final int mediaBitrate; - private final @Nullable Set adUiElements; - private final @Nullable AdEventListener adEventListener; + private final @Nullable + Set adUiElements; + private final @Nullable + AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; private final List adCallbacks; @@ -328,11 +344,14 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader; private boolean wasSetPlayerCalled; - @Nullable private Player nextPlayer; + @Nullable + private Player nextPlayer; private Object pendingAdRequestContext; private List supportedMimeTypes; - @Nullable private EventListener eventListener; - @Nullable private Player player; + @Nullable + private EventListener eventListener; + @Nullable + private Player player; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercentage; @@ -347,14 +366,23 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking IMA's state. - /** The expected ad group index that IMA should load next. */ + /** + * The expected ad group index that IMA should load next. + */ private int expectedAdGroupIndex; - /** The index of the current ad group that IMA is loading. */ + /** + * The index of the current ad group that IMA is loading. + */ private int adGroupIndex; - /** Whether IMA has sent an ad event to pause content since the last resume content event. */ + /** + * Whether IMA has sent an ad event to pause content since the last resume content event. + */ private boolean imaPausedContent; - /** The current ad playback state. */ - private @ImaAdState int imaAdState; + /** + * The current ad playback state. + */ + private @ImaAdState + int imaAdState; /** * Whether {@link com.google.ads.interactivemedia.v3.api.AdsLoader#contentComplete()} has been * called since starting ad playback. @@ -363,7 +391,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking the player/loader state. - /** Whether the player is playing an ad. */ + /** + * Whether the player is playing an ad. + */ private boolean playingAd; /** * If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET} @@ -386,9 +416,13 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * content progress should increase. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressOffsetMs; - /** Stores the pending content position when a seek operation was intercepted to play an ad. */ + /** + * Stores the pending content position when a seek operation was intercepted to play an ad. + */ private long pendingContentPositionMs; - /** Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. */ + /** + * Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. + */ private boolean sentPendingContentPositionMs; /** @@ -396,10 +430,10 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * *

If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead. * - * @param context The context. + * @param context The context. * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. */ public ImaAdsLoader(Context context, Uri adTagUri) { this( @@ -419,12 +453,13 @@ public ImaAdsLoader(Context context, Uri adTagUri) { /** * Creates a new IMA ads loader. * - * @param context The context. - * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * @param context The context. + * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. * @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to - * use the default settings. If set, the player type and version fields may be overwritten. + * use the default settings. If set, the player type and version fields may + * be overwritten. * @deprecated Use {@link ImaAdsLoader.Builder}. */ @Deprecated @@ -489,8 +524,8 @@ private ImaAdsLoader( } /** - * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by - * this instance. + * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by this + * instance. */ public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() { return adsLoader; @@ -1368,7 +1403,7 @@ private void maybeNotifyInternalError(String name, Exception cause) { private static long[] getAdGroupTimesUs(List cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. - return new long[] {0}; + return new long[]{0}; } int count = cuePoints.size(); @@ -1406,24 +1441,44 @@ private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) { } } - /** Factory for objects provided by the IMA SDK. */ + /** + * Factory for objects provided by the IMA SDK. + */ @VisibleForTesting - /* package */ interface ImaFactory { - /** @see ImaSdkSettings */ + /* package */ interface ImaFactory { + + /** + * @see ImaSdkSettings + */ ImaSdkSettings createImaSdkSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() + */ AdsRenderingSettings createAdsRenderingSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() + */ AdDisplayContainer createAdDisplayContainer(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() + */ AdsRequest createAdsRequest(); - /** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */ + + /** + * @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) + */ com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer); } - /** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */ + /** + * Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. + */ private static final class DefaultImaFactory implements ImaFactory { + @Override public ImaSdkSettings createImaSdkSettings() { return ImaSdkFactory.getInstance().createImaSdkSettings(); diff --git a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DemoApplication.java index d83d7076..46d22338 100644 --- a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -64,19 +64,25 @@ public void onCreate() { userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); } - /** Returns a {@link DataSource.Factory}. */ + /** + * Returns a {@link DataSource.Factory}. + */ public DataSource.Factory buildDataSourceFactory() { DefaultDataSourceFactory upstreamFactory = new DefaultDataSourceFactory(this, buildHttpDataSourceFactory()); return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache()); } - /** Returns a {@link HttpDataSource.Factory}. */ + /** + * Returns a {@link HttpDataSource.Factory}. + */ public HttpDataSource.Factory buildHttpDataSourceFactory() { return new DefaultHttpDataSourceFactory(userAgent); } - /** Returns whether extension renderers should be used. */ + /** + * Returns whether extension renderers should be used. + */ public boolean useExtensionRenderers() { return "withExtensions".equals(BuildConfig.FLAVOR); } @@ -86,8 +92,8 @@ public RenderersFactory buildRenderersFactory(boolean preferExtensionRenderer) { int extensionRendererMode = useExtensionRenderers() ? (preferExtensionRenderer - ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) + ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; return new DefaultRenderersFactory(/* context= */ this) .setExtensionRendererMode(extensionRendererMode); diff --git a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index c3909dfe..50ff317d 100644 --- a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -25,7 +25,9 @@ import com.google.android.exoplayer2.util.Util; import java.util.List; -/** A service for downloading media. */ +/** + * A service for downloading media. + */ public class DemoDownloadService extends DownloadService { private static final String CHANNEL_ID = "download_channel"; diff --git a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DownloadTracker.java index 143eda93..8c96e5f3 100644 --- a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -39,13 +39,19 @@ import java.util.HashMap; import java.util.concurrent.CopyOnWriteArraySet; -/** Tracks media that has been downloaded. */ +/** + * Tracks media that has been downloaded. + */ public class DownloadTracker { - /** Listens for changes in the tracked downloads. */ + /** + * Listens for changes in the tracked downloads. + */ public interface Listener { - /** Called when the tracked downloads changed. */ + /** + * Called when the tracked downloads changed. + */ void onDownloadsChanged(); } @@ -58,7 +64,8 @@ public interface Listener { private final DownloadIndex downloadIndex; private final DefaultTrackSelector.Parameters trackSelectorParameters; - @Nullable private StartDownloadDialogHelper startDownloadDialogHelper; + @Nullable + private StartDownloadDialogHelper startDownloadDialogHelper; public DownloadTracker( Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) { @@ -159,8 +166,8 @@ public void onDownloadRemoved(DownloadManager downloadManager, Download download private final class StartDownloadDialogHelper implements DownloadHelper.Callback, - DialogInterface.OnClickListener, - DialogInterface.OnDismissListener { + DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { private final FragmentManager fragmentManager; private final DownloadHelper downloadHelper; diff --git a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 7cc2640b..898b7e8c 100644 --- a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -81,12 +81,13 @@ import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; - import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; -/** An activity that plays media using {@link SimpleExoPlayer}. */ +/** + * An activity that plays media using {@link SimpleExoPlayer}. + */ public class PlayerActivity extends AppCompatActivity implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener { @@ -138,6 +139,7 @@ public class PlayerActivity extends AppCompatActivity private static final String KEY_AUTO_PLAY = "auto_play"; private static final CookieManager DEFAULT_COOKIE_MANAGER; + static { DEFAULT_COOKIE_MANAGER = new CookieManager(); DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); @@ -393,9 +395,9 @@ private void initializePlayer() { lastSeenTrackGroupArray = null; player = - new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory) - .setTrackSelector(trackSelector) - .build(); + new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory) + .setTrackSelector(trackSelector) + .build(); player.addListener(new PlayerEventListener()); player.setPlayWhenReady(startAutoPlay); player.addAnalyticsListener(new EventLogger(trackSelector)); @@ -409,7 +411,7 @@ private void initializePlayer() { CustomerVideoData customerVideoData = new CustomerVideoData(); customerVideoData.setVideoTitle(intent.getStringExtra(VIDEO_TITLE_EXTRA)); muxStats = new MuxStatsExoPlayer( - this, player, "demo-player", customerPlayerData, customerVideoData); + this, player, "demo-player", customerPlayerData, customerVideoData); Point size = new Point(); getWindowManager().getDefaultDisplay().getSize(size); muxStats.setScreenSize(size.x, size.y); @@ -438,7 +440,7 @@ private MediaSource createTopLevelMediaSource(Intent intent) { UriSample[] samples = intentAsSample instanceof Sample.PlaylistSample ? ((Sample.PlaylistSample) intentAsSample).children - : new UriSample[] {(UriSample) intentAsSample}; + : new UriSample[]{(UriSample) intentAsSample}; for (UriSample sample : samples) { if (!Util.checkCleartextTrafficPermitted(sample.uri)) { @@ -582,7 +584,9 @@ private void clearStartPosition() { startPosition = C.TIME_UNSET; } - /** Returns a new DataSource factory. */ + /** + * Returns a new DataSource factory. + */ private DataSource.Factory buildDataSourceFactory() { return ((DemoApplication) getApplication()).buildDataSourceFactory(); } diff --git a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/Sample.java b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/Sample.java index 85530b99..b6dff993 100644 --- a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/Sample.java +++ b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/Sample.java @@ -63,8 +63,10 @@ public static UriSample createFromIntent(Uri uri, Intent intent, String extrasKe public final boolean isLive; public final DrmInfo drmInfo; public final Uri adTagUri; - @Nullable public final String sphericalStereoMode; - @Nullable SubtitleInfo subtitleInfo; + @Nullable + public final String sphericalStereoMode; + @Nullable + SubtitleInfo subtitleInfo; public UriSample( String name, @@ -192,7 +194,8 @@ public static SubtitleInfo createFromIntent(Intent intent, String extrasKeySuffi public final Uri uri; public final String mimeType; - @Nullable public final String language; + @Nullable + public final String language; public SubtitleInfo(Uri uri, String mimeType, @Nullable String language) { this.uri = Assertions.checkNotNull(uri); @@ -226,7 +229,8 @@ public static Sample createFromIntent(Intent intent) { } } - @Nullable public final String name; + @Nullable + public final String name; public Sample(String name) { this.name = name; diff --git a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index cdce29aa..53e1f1b3 100644 --- a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -57,7 +57,9 @@ import java.util.Collections; import java.util.List; -/** An activity for selecting from a list of media samples. */ +/** + * An activity for selecting from a list of media samples. + */ public class SampleChooserActivity extends AppCompatActivity implements DownloadTracker.Listener, OnChildClickListener { @@ -83,7 +85,7 @@ public void onCreate(Bundle savedInstanceState) { String dataUri = intent.getDataString(); String[] uris; if (dataUri != null) { - uris = new String[] {dataUri}; + uris = new String[]{dataUri}; } else { ArrayList uriList = new ArrayList<>(); AssetManager assetManager = getAssets(); diff --git a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java index 9e800938..d1b49e77 100644 --- a/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java +++ b/demo/src/r2_11_1/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java @@ -43,7 +43,9 @@ import java.util.Collections; import java.util.List; -/** Dialog to select tracks. */ +/** + * Dialog to select tracks. + */ public final class TrackSelectionDialog extends DialogFragment { private final SparseArray tabFragments; @@ -79,9 +81,9 @@ public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) { * Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be * automatically updated when tracks are selected. * - * @param trackSelector The {@link DefaultTrackSelector}. + * @param trackSelector The {@link DefaultTrackSelector}. * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is - * dismissed. + * dismissed. */ public static TrackSelectionDialog createForTrackSelector( DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) { @@ -121,16 +123,17 @@ public static TrackSelectionDialog createForTrackSelector( /** * Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}. * - * @param titleId The resource id of the dialog title. - * @param mappedTrackInfo The {@link MappedTrackInfo} to display. - * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial - * track selection. + * @param titleId The resource id of the dialog title. + * @param mappedTrackInfo The {@link MappedTrackInfo} to display. + * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the + * initial track selection. * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track) - * can be made. - * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. - * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected. - * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is - * dismissed. + * can be made. + * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. + * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are + * selected. + * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog + * is dismissed. */ public static TrackSelectionDialog createForMappedTrackInfoAndParameters( int titleId, @@ -306,7 +309,9 @@ public CharSequence getPageTitle(int position) { } } - /** Fragment to show a track selection in tab of the track selection dialog. */ + /** + * Fragment to show a track selection in tab of the track selection dialog. + */ public static final class TrackSelectionViewFragment extends Fragment implements TrackSelectionView.TrackSelectionListener { diff --git a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java index d83d7076..46d22338 100644 --- a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -64,19 +64,25 @@ public void onCreate() { userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); } - /** Returns a {@link DataSource.Factory}. */ + /** + * Returns a {@link DataSource.Factory}. + */ public DataSource.Factory buildDataSourceFactory() { DefaultDataSourceFactory upstreamFactory = new DefaultDataSourceFactory(this, buildHttpDataSourceFactory()); return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache()); } - /** Returns a {@link HttpDataSource.Factory}. */ + /** + * Returns a {@link HttpDataSource.Factory}. + */ public HttpDataSource.Factory buildHttpDataSourceFactory() { return new DefaultHttpDataSourceFactory(userAgent); } - /** Returns whether extension renderers should be used. */ + /** + * Returns whether extension renderers should be used. + */ public boolean useExtensionRenderers() { return "withExtensions".equals(BuildConfig.FLAVOR); } @@ -86,8 +92,8 @@ public RenderersFactory buildRenderersFactory(boolean preferExtensionRenderer) { int extensionRendererMode = useExtensionRenderers() ? (preferExtensionRenderer - ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) + ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; return new DefaultRenderersFactory(/* context= */ this) .setExtensionRendererMode(extensionRendererMode); diff --git a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index c3909dfe..50ff317d 100644 --- a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -25,7 +25,9 @@ import com.google.android.exoplayer2.util.Util; import java.util.List; -/** A service for downloading media. */ +/** + * A service for downloading media. + */ public class DemoDownloadService extends DownloadService { private static final String CHANNEL_ID = "download_channel"; diff --git a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java index 143eda93..8c96e5f3 100644 --- a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -39,13 +39,19 @@ import java.util.HashMap; import java.util.concurrent.CopyOnWriteArraySet; -/** Tracks media that has been downloaded. */ +/** + * Tracks media that has been downloaded. + */ public class DownloadTracker { - /** Listens for changes in the tracked downloads. */ + /** + * Listens for changes in the tracked downloads. + */ public interface Listener { - /** Called when the tracked downloads changed. */ + /** + * Called when the tracked downloads changed. + */ void onDownloadsChanged(); } @@ -58,7 +64,8 @@ public interface Listener { private final DownloadIndex downloadIndex; private final DefaultTrackSelector.Parameters trackSelectorParameters; - @Nullable private StartDownloadDialogHelper startDownloadDialogHelper; + @Nullable + private StartDownloadDialogHelper startDownloadDialogHelper; public DownloadTracker( Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) { @@ -159,8 +166,8 @@ public void onDownloadRemoved(DownloadManager downloadManager, Download download private final class StartDownloadDialogHelper implements DownloadHelper.Callback, - DialogInterface.OnClickListener, - DialogInterface.OnDismissListener { + DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { private final FragmentManager fragmentManager; private final DownloadHelper downloadHelper; diff --git a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java index bf154cda..5e61c4bf 100644 --- a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -84,15 +84,15 @@ import com.mux.stats.sdk.core.MuxSDKViewOrientation; import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; -import com.mux.stats.sdk.muxstats.AdsImaSDKListener; import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; - import java.lang.reflect.Constructor; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; -/** An activity that plays media using {@link SimpleExoPlayer}. */ +/** + * An activity that plays media using {@link SimpleExoPlayer}. + */ public class PlayerActivity extends AppCompatActivity implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener { @@ -144,6 +144,7 @@ public class PlayerActivity extends AppCompatActivity private static final String KEY_AUTO_PLAY = "auto_play"; private static final CookieManager DEFAULT_COOKIE_MANAGER; + static { DEFAULT_COOKIE_MANAGER = new CookieManager(); DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); @@ -398,9 +399,9 @@ private void initializePlayer() { lastSeenTrackGroupArray = null; player = - new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory) - .setTrackSelector(trackSelector) - .build(); + new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory) + .setTrackSelector(trackSelector) + .build(); player.addListener(new PlayerEventListener()); player.setPlayWhenReady(startAutoPlay); player.addAnalyticsListener(new EventLogger(trackSelector)); @@ -414,7 +415,7 @@ private void initializePlayer() { CustomerVideoData customerVideoData = new CustomerVideoData(); customerVideoData.setVideoTitle(intent.getStringExtra(VIDEO_TITLE_EXTRA)); muxStats = new MuxStatsExoPlayer( - this, player, "demo-player", customerPlayerData, customerVideoData); + this, player, "demo-player", customerPlayerData, customerVideoData); Point size = new Point(); getWindowManager().getDefaultDisplay().getSize(size); muxStats.setScreenSize(size.x, size.y); @@ -453,7 +454,7 @@ private MediaSource createTopLevelMediaSource(Intent intent) { UriSample[] samples = intentAsSample instanceof Sample.PlaylistSample ? ((Sample.PlaylistSample) intentAsSample).children - : new UriSample[] {(UriSample) intentAsSample}; + : new UriSample[]{(UriSample) intentAsSample}; boolean seenAdsTagUri = false; for (UriSample sample : samples) { @@ -632,11 +633,16 @@ private void clearStartPosition() { startPosition = C.TIME_UNSET; } - /** Returns a new DataSource factory. */ + /** + * Returns a new DataSource factory. + */ private DataSource.Factory buildDataSourceFactory() { return ((DemoApplication) getApplication()).buildDataSourceFactory(); } - /** Returns an ads media source, reusing the ads loader if one exists. */ + + /** + * Returns an ads media source, reusing the ads loader if one exists. + */ @Nullable private MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { // Load the extension source using reflection so the demo app doesn't have to depend on it. @@ -647,25 +653,25 @@ private MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) // Full class names used so the LINT.IfChange rule triggers should any of the classes move. // LINT.IfChange Constructor loaderConstructor = - loaderClass - .asSubclass(AdsLoader.class) - .getConstructor(android.content.Context.class, android.net.Uri.class); + loaderClass + .asSubclass(AdsLoader.class) + .getConstructor(android.content.Context.class, android.net.Uri.class); // LINT.ThenChange(../../../../../../../../proguard-rules.txt) adsLoader = loaderConstructor.newInstance(this, adTagUri); } MediaSourceFactory adMediaSourceFactory = - new MediaSourceFactory() { - @Override - public MediaSource createMediaSource(Uri uri) { - return PlayerActivity.this.createLeafMediaSource( - uri, /* extension=*/ null, DrmSessionManager.getDummyDrmSessionManager()); - } - - @Override - public int[] getSupportedTypes() { - return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; - } - }; + new MediaSourceFactory() { + @Override + public MediaSource createMediaSource(Uri uri) { + return PlayerActivity.this.createLeafMediaSource( + uri, /* extension=*/ null, DrmSessionManager.getDummyDrmSessionManager()); + } + + @Override + public int[] getSupportedTypes() { + return new int[]{C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; + } + }; // Because of how this is loaded via reflection, we know that this will be // a ImaAdsLoader, so cast it over so that we can get a reference to the // real IMA AdsLoader instance. diff --git a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/Sample.java b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/Sample.java index 85530b99..b6dff993 100644 --- a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/Sample.java +++ b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/Sample.java @@ -63,8 +63,10 @@ public static UriSample createFromIntent(Uri uri, Intent intent, String extrasKe public final boolean isLive; public final DrmInfo drmInfo; public final Uri adTagUri; - @Nullable public final String sphericalStereoMode; - @Nullable SubtitleInfo subtitleInfo; + @Nullable + public final String sphericalStereoMode; + @Nullable + SubtitleInfo subtitleInfo; public UriSample( String name, @@ -192,7 +194,8 @@ public static SubtitleInfo createFromIntent(Intent intent, String extrasKeySuffi public final Uri uri; public final String mimeType; - @Nullable public final String language; + @Nullable + public final String language; public SubtitleInfo(Uri uri, String mimeType, @Nullable String language) { this.uri = Assertions.checkNotNull(uri); @@ -226,7 +229,8 @@ public static Sample createFromIntent(Intent intent) { } } - @Nullable public final String name; + @Nullable + public final String name; public Sample(String name) { this.name = name; diff --git a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index cdce29aa..53e1f1b3 100644 --- a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -57,7 +57,9 @@ import java.util.Collections; import java.util.List; -/** An activity for selecting from a list of media samples. */ +/** + * An activity for selecting from a list of media samples. + */ public class SampleChooserActivity extends AppCompatActivity implements DownloadTracker.Listener, OnChildClickListener { @@ -83,7 +85,7 @@ public void onCreate(Bundle savedInstanceState) { String dataUri = intent.getDataString(); String[] uris; if (dataUri != null) { - uris = new String[] {dataUri}; + uris = new String[]{dataUri}; } else { ArrayList uriList = new ArrayList<>(); AssetManager assetManager = getAssets(); diff --git a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java index 9e800938..d1b49e77 100644 --- a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java +++ b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java @@ -43,7 +43,9 @@ import java.util.Collections; import java.util.List; -/** Dialog to select tracks. */ +/** + * Dialog to select tracks. + */ public final class TrackSelectionDialog extends DialogFragment { private final SparseArray tabFragments; @@ -79,9 +81,9 @@ public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) { * Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be * automatically updated when tracks are selected. * - * @param trackSelector The {@link DefaultTrackSelector}. + * @param trackSelector The {@link DefaultTrackSelector}. * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is - * dismissed. + * dismissed. */ public static TrackSelectionDialog createForTrackSelector( DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) { @@ -121,16 +123,17 @@ public static TrackSelectionDialog createForTrackSelector( /** * Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}. * - * @param titleId The resource id of the dialog title. - * @param mappedTrackInfo The {@link MappedTrackInfo} to display. - * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial - * track selection. + * @param titleId The resource id of the dialog title. + * @param mappedTrackInfo The {@link MappedTrackInfo} to display. + * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the + * initial track selection. * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track) - * can be made. - * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. - * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected. - * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is - * dismissed. + * can be made. + * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. + * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are + * selected. + * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog + * is dismissed. */ public static TrackSelectionDialog createForMappedTrackInfoAndParameters( int titleId, @@ -306,7 +309,9 @@ public CharSequence getPageTitle(int position) { } } - /** Fragment to show a track selection in tab of the track selection dialog. */ + /** + * Fragment to show a track selection in tab of the track selection dialog. + */ public static final class TrackSelectionViewFragment extends Fragment implements TrackSelectionView.TrackSelectionListener { diff --git a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 98dbef7c..ac3cb4a4 100644 --- a/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/demo/src/r2_11_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -81,31 +81,36 @@ * #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling * {@link #release()}. * - *

The IMA SDK can take into account video control overlay views when calculating ad viewability. - * For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} and {@link - * AdViewProvider#getAdOverlayViews()}. + *

The IMA SDK can take into account video control overlay views when calculating ad + * viewability. For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} + * and {@link AdViewProvider#getAdOverlayViews()}. */ public final class ImaAdsLoader implements Player.EventListener, - AdsLoader, - VideoAdPlayer, - ContentProgressProvider, - AdErrorListener, - AdsLoadedListener, - AdEventListener { + AdsLoader, + VideoAdPlayer, + ContentProgressProvider, + AdErrorListener, + AdsLoadedListener, + AdEventListener { static { ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); } - /** Builder for {@link ImaAdsLoader}. */ + /** + * Builder for {@link ImaAdsLoader}. + */ public static final class Builder { private final Context context; - @Nullable private ImaSdkSettings imaSdkSettings; - @Nullable private AdEventListener adEventListener; - @Nullable private Set adUiElements; + @Nullable + private ImaSdkSettings imaSdkSettings; + @Nullable + private AdEventListener adEventListener; + @Nullable + private Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; private int mediaBitrate; @@ -141,8 +146,7 @@ public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { } /** - * Sets a listener for ad events that will be passed to {@link - * AdsManager#addAdEventListener(AdEventListener)}. + * Sets a listener for ad events that will be passed to {@link AdsManager#addAdEventListener(AdEventListener)}. * * @param adEventListener The ad event listener. * @return This builder, for convenience. @@ -208,7 +212,7 @@ public Builder setMaxMediaBitrate(int bitrate) { * setting is {@code true}. * * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on - * Android TV devices. + * Android TV devices. * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ @@ -218,7 +222,7 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila } @VisibleForTesting - /* package */ Builder setImaFactory(ImaFactory imaFactory) { + /* package */ Builder setImaFactory(ImaFactory imaFactory) { this.imaFactory = Assertions.checkNotNull(imaFactory); return this; } @@ -226,9 +230,8 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila /** * Returns a new {@link ImaAdsLoader} for the specified ad tag. * - * @param adTagUri The URI of a compatible ad tag to load. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * information on compatible ad tags. + * @param adTagUri The URI of a compatible ad tag to load. See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for information on compatible ad tags. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdTag(Uri adTagUri) { @@ -250,7 +253,7 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) { * Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response. * * @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of - * making a request via an ad tag URL. + * making a request via an ad tag URL. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdsResponse(String adsResponse) { @@ -280,7 +283,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private static final String IMA_SDK_SETTINGS_PLAYER_TYPE = "google/exo.ext.ima"; private static final String IMA_SDK_SETTINGS_PLAYER_VERSION = ExoPlayerLibraryInfo.VERSION; - /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ + /** + * The value used in {@link VideoProgressUpdate}s to indicate an unset duration. + */ private static final long IMA_DURATION_UNSET = -1L; /** @@ -289,17 +294,24 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final long END_OF_CONTENT_POSITION_THRESHOLD_MS = 5000; - /** The maximum duration before an ad break that IMA may start preloading the next ad. */ + /** + * The maximum duration before an ad break that IMA may start preloading the next ad. + */ private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000; private static final int TIMEOUT_UNSET = -1; private static final int BITRATE_UNSET = -1; - /** The state of ad playback. */ + /** + * The state of ad playback. + */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) - private @interface ImaAdState {} + private @interface ImaAdState { + + } + /** * The ad playback state when IMA is not playing an ad. */ @@ -313,14 +325,18 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final int IMA_AD_STATE_PAUSED = 2; - @Nullable private final Uri adTagUri; - @Nullable private final String adsResponse; + @Nullable + private final Uri adTagUri; + @Nullable + private final String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; private final boolean focusSkipButtonWhenAvailable; private final int mediaBitrate; - @Nullable private final Set adUiElements; - @Nullable private final AdEventListener adEventListener; + @Nullable + private final Set adUiElements; + @Nullable + private final AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; private final List adCallbacks; @@ -328,11 +344,14 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader; private boolean wasSetPlayerCalled; - @Nullable private Player nextPlayer; + @Nullable + private Player nextPlayer; private Object pendingAdRequestContext; private List supportedMimeTypes; - @Nullable private EventListener eventListener; - @Nullable private Player player; + @Nullable + private EventListener eventListener; + @Nullable + private Player player; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercentage; @@ -347,14 +366,23 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking IMA's state. - /** The expected ad group index that IMA should load next. */ + /** + * The expected ad group index that IMA should load next. + */ private int expectedAdGroupIndex; - /** The index of the current ad group that IMA is loading. */ + /** + * The index of the current ad group that IMA is loading. + */ private int adGroupIndex; - /** Whether IMA has sent an ad event to pause content since the last resume content event. */ + /** + * Whether IMA has sent an ad event to pause content since the last resume content event. + */ private boolean imaPausedContent; - /** The current ad playback state. */ - private @ImaAdState int imaAdState; + /** + * The current ad playback state. + */ + private @ImaAdState + int imaAdState; /** * Whether {@link com.google.ads.interactivemedia.v3.api.AdsLoader#contentComplete()} has been * called since starting ad playback. @@ -363,7 +391,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking the player/loader state. - /** Whether the player is playing an ad. */ + /** + * Whether the player is playing an ad. + */ private boolean playingAd; /** * If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET} @@ -386,9 +416,13 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * content progress should increase. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressOffsetMs; - /** Stores the pending content position when a seek operation was intercepted to play an ad. */ + /** + * Stores the pending content position when a seek operation was intercepted to play an ad. + */ private long pendingContentPositionMs; - /** Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. */ + /** + * Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. + */ private boolean sentPendingContentPositionMs; /** @@ -396,10 +430,10 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * *

If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead. * - * @param context The context. + * @param context The context. * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. */ public ImaAdsLoader(Context context, Uri adTagUri) { this( @@ -419,12 +453,13 @@ public ImaAdsLoader(Context context, Uri adTagUri) { /** * Creates a new IMA ads loader. * - * @param context The context. - * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * @param context The context. + * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. * @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to - * use the default settings. If set, the player type and version fields may be overwritten. + * use the default settings. If set, the player type and version fields may + * be overwritten. * @deprecated Use {@link ImaAdsLoader.Builder}. */ @Deprecated @@ -489,8 +524,8 @@ private ImaAdsLoader( } /** - * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by - * this instance. + * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by this + * instance. */ public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() { return adsLoader; @@ -1372,7 +1407,7 @@ private void maybeNotifyInternalError(String name, Exception cause) { private static long[] getAdGroupTimesUs(List cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. - return new long[] {0}; + return new long[]{0}; } int count = cuePoints.size(); @@ -1410,24 +1445,44 @@ private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) { } } - /** Factory for objects provided by the IMA SDK. */ + /** + * Factory for objects provided by the IMA SDK. + */ @VisibleForTesting - /* package */ interface ImaFactory { - /** @see ImaSdkSettings */ + /* package */ interface ImaFactory { + + /** + * @see ImaSdkSettings + */ ImaSdkSettings createImaSdkSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() + */ AdsRenderingSettings createAdsRenderingSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() + */ AdDisplayContainer createAdDisplayContainer(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() + */ AdsRequest createAdsRequest(); - /** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */ + + /** + * @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) + */ com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer); } - /** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */ + /** + * Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. + */ private static final class DefaultImaFactory implements ImaFactory { + @Override public ImaSdkSettings createImaSdkSettings() { return ImaSdkFactory.getInstance().createImaSdkSettings(); diff --git a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index c462c14c..842668c0 100644 --- a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -30,7 +30,9 @@ import com.google.android.exoplayer2.util.Util; import java.util.List; -/** A service for downloading media. */ +/** + * A service for downloading media. + */ public class DemoDownloadService extends DownloadService { private static final int JOB_ID = 1; diff --git a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DemoUtil.java b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DemoUtil.java index c46e0865..bcfb93df 100644 --- a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DemoUtil.java +++ b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DemoUtil.java @@ -39,7 +39,9 @@ import java.util.concurrent.Executors; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** Utility methods for the demo app. */ +/** + * Utility methods for the demo app. + */ public final class DemoUtil { public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel"; @@ -51,14 +53,22 @@ public final class DemoUtil { private static DataSource.@MonotonicNonNull Factory dataSourceFactory; private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory; - private static @MonotonicNonNull DatabaseProvider databaseProvider; - private static @MonotonicNonNull File downloadDirectory; - private static @MonotonicNonNull Cache downloadCache; - private static @MonotonicNonNull DownloadManager downloadManager; - private static @MonotonicNonNull DownloadTracker downloadTracker; - private static @MonotonicNonNull DownloadNotificationHelper downloadNotificationHelper; - - /** Returns whether extension renderers should be used. */ + private static @MonotonicNonNull + DatabaseProvider databaseProvider; + private static @MonotonicNonNull + File downloadDirectory; + private static @MonotonicNonNull + Cache downloadCache; + private static @MonotonicNonNull + DownloadManager downloadManager; + private static @MonotonicNonNull + DownloadTracker downloadTracker; + private static @MonotonicNonNull + DownloadNotificationHelper downloadNotificationHelper; + + /** + * Returns whether extension renderers should be used. + */ public static boolean useExtensionRenderers() { // return BuildConfig.USE_DECODER_EXTENSIONS; return false; @@ -70,8 +80,8 @@ public static RenderersFactory buildRenderersFactory( int extensionRendererMode = useExtensionRenderers() ? (preferExtensionRenderer - ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) + ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; return new DefaultRenderersFactory(context.getApplicationContext()) .setExtensionRendererMode(extensionRendererMode); @@ -87,7 +97,9 @@ public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Conte return httpDataSourceFactory; } - /** Returns a {@link DataSource.Factory}. */ + /** + * Returns a {@link DataSource.Factory}. + */ public static synchronized DataSource.Factory getDataSourceFactory(Context context) { if (dataSourceFactory == null) { context = context.getApplicationContext(); @@ -193,5 +205,6 @@ private static CacheDataSource.Factory buildReadOnlyCacheDataSource( .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR); } - private DemoUtil() {} + private DemoUtil() { + } } diff --git a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DownloadTracker.java index 07f4dd2f..5f75ddf5 100644 --- a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -53,13 +53,19 @@ import java.util.HashMap; import java.util.concurrent.CopyOnWriteArraySet; -/** Tracks media that has been downloaded. */ +/** + * Tracks media that has been downloaded. + */ public class DownloadTracker { - /** Listens for changes in the tracked downloads. */ + /** + * Listens for changes in the tracked downloads. + */ public interface Listener { - /** Called when the tracked downloads changed. */ + /** + * Called when the tracked downloads changed. + */ void onDownloadsChanged(); } @@ -72,7 +78,8 @@ public interface Listener { private final DownloadIndex downloadIndex; private final DefaultTrackSelector.Parameters trackSelectorParameters; - @Nullable private StartDownloadDialogHelper startDownloadDialogHelper; + @Nullable + private StartDownloadDialogHelper startDownloadDialogHelper; public DownloadTracker( Context context, @@ -163,8 +170,8 @@ public void onDownloadRemoved( private final class StartDownloadDialogHelper implements DownloadHelper.Callback, - DialogInterface.OnClickListener, - DialogInterface.OnDismissListener { + DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { private final FragmentManager fragmentManager; private final DownloadHelper downloadHelper; @@ -173,7 +180,8 @@ private final class StartDownloadDialogHelper private TrackSelectionDialog trackSelectionDialog; private MappedTrackInfo mappedTrackInfo; private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask; - @Nullable private byte[] keySetId; + @Nullable + private byte[] keySetId; public StartDownloadDialogHelper( FragmentManager fragmentManager, DownloadHelper downloadHelper, MediaItem mediaItem) { @@ -368,7 +376,9 @@ private DownloadRequest buildDownloadRequest() { } } - /** Downloads a Widevine offline license in a background thread. */ + /** + * Downloads a Widevine offline license in a background thread. + */ @RequiresApi(18) private static final class WidevineOfflineLicenseFetchTask extends AsyncTask { @@ -378,8 +388,10 @@ private static final class WidevineOfflineLicenseFetchTask extends AsyncTask createMediaItemsFromIntent(Intent intent) { List mediaItems = new ArrayList<>(); if (ACTION_VIEW_LIST.equals(intent.getAction())) { @@ -82,7 +86,9 @@ public static List createMediaItemsFromIntent(Intent intent) { return mediaItems; } - /** Populates the intent with the given list of {@link MediaItem media items}. */ + /** + * Populates the intent with the given list of {@link MediaItem media items}. + */ public static void addToIntent(List mediaItems, Intent intent) { Assertions.checkArgument(!mediaItems.isEmpty()); if (mediaItems.size() == 1) { diff --git a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 9e6dd00b..f5e95466 100644 --- a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -72,7 +72,9 @@ import java.util.Collections; import java.util.List; -/** An activity that plays media using {@link SimpleExoPlayer}. */ +/** + * An activity that plays media using {@link SimpleExoPlayer}. + */ public class PlayerActivity extends AppCompatActivity implements OnClickListener, PlaybackPreparer, StyledPlayerControlView.VisibilityListener { @@ -293,7 +295,9 @@ protected void setContentView() { setContentView(R.layout.player_activity); } - /** @return Whether initialization was successful. */ + /** + * @return Whether initialization was successful. + */ protected boolean initializePlayer() { if (player == null) { Intent intent = getIntent(); @@ -333,7 +337,7 @@ protected boolean initializePlayer() { CustomerVideoData customerVideoData = new CustomerVideoData(); customerVideoData.setVideoTitle(intent.getStringExtra(VIDEO_TITLE_EXTRA)); muxStats = new MuxStatsExoPlayer( - this, player, "demo-player", customerPlayerData, customerVideoData); + this, player, "demo-player", customerPlayerData, customerVideoData); Point size = new Point(); getWindowManager().getDefaultDisplay().getSize(size); muxStats.setScreenSize(size.x, size.y); diff --git a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index ea5b38ce..e1b3e5ce 100644 --- a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.AssetManager; import android.net.Uri; import android.os.AsyncTask; @@ -66,7 +65,9 @@ import java.util.List; import java.util.Map; -/** An activity for selecting from a list of media samples. */ +/** + * An activity for selecting from a list of media samples. + */ public class SampleChooserActivity extends AppCompatActivity implements DownloadTracker.Listener, OnChildClickListener { @@ -94,7 +95,7 @@ public void onCreate(Bundle savedInstanceState) { Intent intent = getIntent(); String dataUri = intent.getDataString(); if (dataUri != null) { - uris = new String[] {dataUri}; + uris = new String[]{dataUri}; } else { ArrayList uriList = new ArrayList<>(); AssetManager assetManager = getAssets(); diff --git a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java index d3f9b388..100aa2bb 100644 --- a/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java +++ b/demo/src/r2_12_1/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java @@ -44,7 +44,9 @@ import java.util.Collections; import java.util.List; -/** Dialog to select tracks. */ +/** + * Dialog to select tracks. + */ public final class TrackSelectionDialog extends DialogFragment { private final SparseArray tabFragments; @@ -80,9 +82,9 @@ public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) { * Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be * automatically updated when tracks are selected. * - * @param trackSelector The {@link DefaultTrackSelector}. + * @param trackSelector The {@link DefaultTrackSelector}. * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is - * dismissed. + * dismissed. */ public static TrackSelectionDialog createForTrackSelector( DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) { @@ -122,16 +124,17 @@ public static TrackSelectionDialog createForTrackSelector( /** * Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}. * - * @param titleId The resource id of the dialog title. - * @param mappedTrackInfo The {@link MappedTrackInfo} to display. - * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial - * track selection. + * @param titleId The resource id of the dialog title. + * @param mappedTrackInfo The {@link MappedTrackInfo} to display. + * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the + * initial track selection. * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track) - * can be made. - * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. - * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected. - * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is - * dismissed. + * can be made. + * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. + * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are + * selected. + * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog + * is dismissed. */ public static TrackSelectionDialog createForMappedTrackInfoAndParameters( int titleId, @@ -306,7 +309,9 @@ public CharSequence getPageTitle(int position) { } } - /** Fragment to show a track selection in tab of the track selection dialog. */ + /** + * Fragment to show a track selection in tab of the track selection dialog. + */ public static final class TrackSelectionViewFragment extends Fragment implements TrackSelectionView.TrackSelectionListener { diff --git a/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 26a60d33..03ee33e0 100644 --- a/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -59,9 +59,9 @@ /** * DataSource without intermediate buffer based on Cronet API set using UrlRequest. * - *

Note: HTTP request headers will be set using all parameters passed via (in order of decreasing - * priority) the {@code dataSpec}, {@link #setRequestProperty} and the default parameters used to - * construct the instance. + *

Note: HTTP request headers will be set using all parameters passed via (in order of + * decreasing priority) the {@code dataSpec}, {@link #setRequestProperty} and the default parameters + * used to construct the instance. */ public class CronetDataSource extends BaseDataSource implements HttpDataSource { @@ -119,12 +119,14 @@ public OpenException(String errorMessage, DataSpec dataSpec, int cronetConnectio private final int readTimeoutMs; private final boolean resetTimeoutOnRedirects; private final boolean handleSetCookieRequests; - @Nullable private final RequestProperties defaultRequestProperties; + @Nullable + private final RequestProperties defaultRequestProperties; private final RequestProperties requestProperties; private final ConditionVariable operation; private final Clock clock; - @Nullable private Predicate contentTypePredicate; + @Nullable + private Predicate contentTypePredicate; // Accessed by the calling thread only. private boolean opened; @@ -133,18 +135,23 @@ public OpenException(String errorMessage, DataSpec dataSpec, int cronetConnectio // Written from the calling thread only. currentUrlRequest.start() calls ensure writes are visible // to reads made by the Cronet thread. - @Nullable private UrlRequest currentUrlRequest; - @Nullable private DataSpec currentDataSpec; + @Nullable + private UrlRequest currentUrlRequest; + @Nullable + private DataSpec currentDataSpec; // Reference written and read by calling thread only. Passed to Cronet thread as a local variable. // operation.open() calls ensure writes into the buffer are visible to reads made by the calling // thread. - @Nullable private ByteBuffer readBuffer; + @Nullable + private ByteBuffer readBuffer; // Written from the Cronet thread only. operation.open() calls ensure writes are visible to reads // made by the calling thread. - @Nullable private UrlResponseInfo responseInfo; - @Nullable private IOException exception; + @Nullable + private UrlResponseInfo responseInfo; + @Nullable + private IOException exception; private boolean finished; private volatile long currentConnectTimeoutMs; @@ -153,11 +160,12 @@ public OpenException(String errorMessage, DataSpec dataSpec, int cronetConnectio * Creates an instance. * * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. + * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This + * may be a direct executor (i.e. executes tasks on the calling thread) in + * order to avoid a thread hop from Cronet's internal network thread to the + * response handling thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling is a fast + * operation when using a direct executor. */ public CronetDataSource(CronetEngine cronetEngine, Executor executor) { this( @@ -172,17 +180,19 @@ public CronetDataSource(CronetEngine cronetEngine, Executor executor) { /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. - * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks + * on the calling thread) in order to avoid a thread hop from + * Cronet's internal network thread to the response handling + * thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling + * is a fast operation when using a direct executor. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. + * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param defaultRequestProperties Optional default {@link RequestProperties} to be sent to the - * server as HTTP headers on every request. + * server as HTTP headers on every request. */ public CronetDataSource( CronetEngine cronetEngine, @@ -205,19 +215,21 @@ public CronetDataSource( /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. - * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks + * on the calling thread) in order to avoid a thread hop from + * Cronet's internal network thread to the response handling + * thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling + * is a fast operation when using a direct executor. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. + * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param defaultRequestProperties Optional default {@link RequestProperties} to be sent to the - * server as HTTP headers on every request. - * @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded to - * the redirect url in the "Cookie" header. + * server as HTTP headers on every request. + * @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded + * to the redirect url in the "Cookie" header. */ public CronetDataSource( CronetEngine cronetEngine, @@ -241,17 +253,19 @@ public CronetDataSource( /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks on + * the calling thread) in order to avoid a thread hop from Cronet's + * internal network thread to the response handling thread. However, + * to avoid slowing down overall network performance, care must be + * taken to make sure response handling is a fast operation when using + * a direct executor. * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the - * predicate then an {@link InvalidContentTypeException} is thrown from {@link - * #open(DataSpec)}. + * predicate then an {@link InvalidContentTypeException} is thrown + * from {@link #open(DataSpec)}. * @deprecated Use {@link #CronetDataSource(CronetEngine, Executor)} and {@link - * #setContentTypePredicate(Predicate)}. + * #setContentTypePredicate(Predicate)}. */ @SuppressWarnings("deprecation") @Deprecated @@ -272,22 +286,24 @@ public CronetDataSource( /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. - * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the - * predicate then an {@link InvalidContentTypeException} is thrown from {@link - * #open(DataSpec)}. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. - * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks + * on the calling thread) in order to avoid a thread hop from + * Cronet's internal network thread to the response handling + * thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling + * is a fast operation when using a direct executor. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by + * the predicate then an {@link InvalidContentTypeException} is + * thrown from {@link #open(DataSpec)}. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. + * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param defaultRequestProperties Optional default {@link RequestProperties} to be sent to the - * server as HTTP headers on every request. + * server as HTTP headers on every request. * @deprecated Use {@link #CronetDataSource(CronetEngine, Executor, int, int, boolean, - * RequestProperties)} and {@link #setContentTypePredicate(Predicate)}. + * RequestProperties)} and {@link #setContentTypePredicate(Predicate)}. */ @SuppressWarnings("deprecation") @Deprecated @@ -313,24 +329,26 @@ public CronetDataSource( /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. - * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the - * predicate then an {@link InvalidContentTypeException} is thrown from {@link - * #open(DataSpec)}. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. - * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks + * on the calling thread) in order to avoid a thread hop from + * Cronet's internal network thread to the response handling + * thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling + * is a fast operation when using a direct executor. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by + * the predicate then an {@link InvalidContentTypeException} is + * thrown from {@link #open(DataSpec)}. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. + * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param defaultRequestProperties Optional default {@link RequestProperties} to be sent to the - * server as HTTP headers on every request. - * @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded to - * the redirect url in the "Cookie" header. + * server as HTTP headers on every request. + * @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded + * to the redirect url in the "Cookie" header. * @deprecated Use {@link #CronetDataSource(CronetEngine, Executor, int, int, boolean, - * RequestProperties, boolean)} and {@link #setContentTypePredicate(Predicate)}. + * RequestProperties, boolean)} and {@link #setContentTypePredicate(Predicate)}. */ @Deprecated public CronetDataSource( @@ -382,7 +400,7 @@ public CronetDataSource( * {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link #open(DataSpec)}. * * @param contentTypePredicate The content type {@link Predicate}, or {@code null} to clear a - * predicate that was previously set. + * predicate that was previously set. */ public void setContentTypePredicate(@Nullable Predicate contentTypePredicate) { this.contentTypePredicate = contentTypePredicate; @@ -567,24 +585,24 @@ public int read(byte[] buffer, int offset, int readLength) throws HttpDataSource * starting at {@code buffer.position()}. Advances the position of the buffer by the number of * bytes read and returns this length. * - *

If there is an error, a {@link HttpDataSourceException} is thrown and the contents of {@code - * buffer} should be ignored. If the exception has error code {@code + *

If there is an error, a {@link HttpDataSourceException} is thrown and the contents of + * {@code buffer} should be ignored. If the exception has error code {@code * HttpDataSourceException.TYPE_READ}, note that Cronet may continue writing into {@code buffer} * after the method has returned. Thus the caller should not attempt to reuse the buffer. * - *

If {@code buffer.remaining()} is zero then 0 is returned. Otherwise, if no data is available - * because the end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is - * returned. Otherwise, the call will block until at least one byte of data has been read and the - * number of bytes read is returned. + *

If {@code buffer.remaining()} is zero then 0 is returned. Otherwise, if no data is + * available because the end of the opened range has been reached, then {@link + * C#RESULT_END_OF_INPUT} is returned. Otherwise, the call will block until at least one byte of + * data has been read and the number of bytes read is returned. * *

Passed buffer must be direct ByteBuffer. If you have a non-direct ByteBuffer, consider the * alternative read method with its backed array. * * @param buffer The ByteBuffer into which the read data should be stored. Must be a direct - * ByteBuffer. + * ByteBuffer. * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if no data is available - * because the end of the opened range has been reached. - * @throws HttpDataSourceException If an error occurs reading from the source. + * because the end of the opened range has been reached. + * @throws HttpDataSourceException If an error occurs reading from the source. * @throws IllegalArgumentException If {@code buffer} is not a direct ByteBuffer. */ public int read(ByteBuffer buffer) throws HttpDataSourceException { @@ -687,13 +705,17 @@ public synchronized void close() { } } - /** Returns current {@link UrlRequest}. May be null if the data source is not opened. */ + /** + * Returns current {@link UrlRequest}. May be null if the data source is not opened. + */ @Nullable protected UrlRequest getCurrentUrlRequest() { return currentUrlRequest; } - /** Returns current {@link UrlResponseInfo}. May be null if the data source is not opened. */ + /** + * Returns current {@link UrlResponseInfo}. May be null if the data source is not opened. + */ @Nullable protected UrlResponseInfo getCurrentUrlResponseInfo() { return responseInfo; @@ -1014,7 +1036,7 @@ public synchronized void onFailed( } if (error instanceof NetworkException && ((NetworkException) error).getErrorCode() - == NetworkException.ERROR_HOSTNAME_NOT_RESOLVED) { + == NetworkException.ERROR_HOSTNAME_NOT_RESOLVED) { exception = new UnknownHostException(); } else { exception = error; diff --git a/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java b/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java index 85c9d09a..1fdfa691 100644 --- a/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java +++ b/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java @@ -45,7 +45,8 @@ public final class CronetDataSourceFactory extends BaseFactory { private final CronetEngineWrapper cronetEngineWrapper; private final Executor executor; - @Nullable private final TransferListener transferListener; + @Nullable + private final TransferListener transferListener; private final int connectTimeoutMs; private final int readTimeoutMs; private final boolean resetTimeoutOnRedirects; @@ -61,9 +62,10 @@ public final class CronetDataSourceFactory extends BaseFactory { * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case no - * suitable CronetEngine can be build. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case + * no suitable CronetEngine can be build. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -89,7 +91,8 @@ public CronetDataSourceFactory( * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. */ public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper, Executor executor) { this(cronetEngineWrapper, executor, DEFAULT_USER_AGENT); @@ -105,8 +108,9 @@ public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper, Executor * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param userAgent A user agent used to create a fallback HttpDataSource if needed. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param userAgent A user agent used to create a fallback HttpDataSource if needed. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, Executor executor, String userAgent) { @@ -131,12 +135,14 @@ public CronetDataSourceFactory( *

If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link * DefaultHttpDataSourceFactory} will be used instead. * - * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. + * @param cronetEngineWrapper A {@link CronetEngineWrapper}. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. - * @param userAgent A user agent used to create a fallback HttpDataSource if needed. + * @param userAgent A user agent used to create a fallback HttpDataSource if + * needed. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -166,13 +172,14 @@ public CronetDataSourceFactory( *

If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, the provided * fallback {@link HttpDataSource.Factory} will be used instead. * - * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. + * @param cronetEngineWrapper A {@link CronetEngineWrapper}. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. - * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case no - * suitable CronetEngine can be build. + * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in + * case no suitable CronetEngine can be build. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -201,10 +208,11 @@ public CronetDataSourceFactory( * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. - * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case no - * suitable CronetEngine can be build. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. + * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case + * no suitable CronetEngine can be build. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -231,8 +239,9 @@ public CronetDataSourceFactory( * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -251,9 +260,10 @@ public CronetDataSourceFactory( * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. - * @param userAgent A user agent used to create a fallback HttpDataSource if needed. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. + * @param userAgent A user agent used to create a fallback HttpDataSource if needed. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -281,13 +291,15 @@ public CronetDataSourceFactory( *

If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link * DefaultHttpDataSourceFactory} will be used instead. * - * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. + * @param cronetEngineWrapper A {@link CronetEngineWrapper}. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. - * @param userAgent A user agent used to create a fallback HttpDataSource if needed. + * @param userAgent A user agent used to create a fallback HttpDataSource if + * needed. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -314,14 +326,15 @@ public CronetDataSourceFactory( *

If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, the provided * fallback {@link HttpDataSource.Factory} will be used instead. * - * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. + * @param cronetEngineWrapper A {@link CronetEngineWrapper}. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. - * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case no - * suitable CronetEngine can be build. + * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in + * case no suitable CronetEngine can be build. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, diff --git a/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java b/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java index 9f709b14..e1314a06 100644 --- a/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java +++ b/demo/src/r2_12_1/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java @@ -40,8 +40,10 @@ public final class CronetEngineWrapper { private static final String TAG = "CronetEngineWrapper"; - @Nullable private final CronetEngine cronetEngine; - @CronetEngineSource private final int cronetEngineSource; + @Nullable + private final CronetEngine cronetEngine; + @CronetEngineSource + private final int cronetEngineSource; /** * Source of {@link CronetEngine}. One of {@link #SOURCE_NATIVE}, {@link #SOURCE_GMS}, {@link @@ -50,7 +52,10 @@ public final class CronetEngineWrapper { @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({SOURCE_NATIVE, SOURCE_GMS, SOURCE_UNKNOWN, SOURCE_USER_PROVIDED, SOURCE_UNAVAILABLE}) - public @interface CronetEngineSource {} + public @interface CronetEngineSource { + + } + /** * Natively bundled Cronet implementation. */ @@ -74,8 +79,8 @@ public final class CronetEngineWrapper { /** * Creates a wrapper for a {@link CronetEngine} which automatically selects the most suitable - * {@link CronetProvider}. Sets wrapper to prefer natively bundled Cronet over GMSCore Cronet - * if both are available. + * {@link CronetProvider}. Sets wrapper to prefer natively bundled Cronet over GMSCore Cronet if + * both are available. * * @param context A context. */ @@ -87,9 +92,9 @@ public CronetEngineWrapper(Context context) { * Creates a wrapper for a {@link CronetEngine} which automatically selects the most suitable * {@link CronetProvider} based on user preference. * - * @param context A context. + * @param context A context. * @param preferGMSCoreCronet Whether Cronet from GMSCore should be preferred over natively - * bundled Cronet if both are available. + * bundled Cronet if both are available. */ public CronetEngineWrapper(Context context, boolean preferGMSCoreCronet) { CronetEngine cronetEngine = null; @@ -164,7 +169,8 @@ public int getCronetEngineSource() { private static class CronetProviderComparator implements Comparator { - @Nullable private final String gmsCoreCronetName; + @Nullable + private final String gmsCoreCronetName; private final boolean preferGMSCoreCronet; // Multi-catch can only be used for API 19+ in this case. @@ -209,8 +215,7 @@ public boolean isGMSCoreProvider(String providerName) { } /** - * Convert Cronet provider name into a sortable preference value. - * Smaller values are preferred. + * Convert Cronet provider name into a sortable preference value. Smaller values are preferred. */ private int evaluateCronetProviderType(String providerName) { if (isNativeProvider(providerName)) { diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index c462c14c..842668c0 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -30,7 +30,9 @@ import com.google.android.exoplayer2.util.Util; import java.util.List; -/** A service for downloading media. */ +/** + * A service for downloading media. + */ public class DemoDownloadService extends DownloadService { private static final int JOB_ID = 1; diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DemoUtil.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DemoUtil.java index c46e0865..bcfb93df 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DemoUtil.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DemoUtil.java @@ -39,7 +39,9 @@ import java.util.concurrent.Executors; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** Utility methods for the demo app. */ +/** + * Utility methods for the demo app. + */ public final class DemoUtil { public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel"; @@ -51,14 +53,22 @@ public final class DemoUtil { private static DataSource.@MonotonicNonNull Factory dataSourceFactory; private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory; - private static @MonotonicNonNull DatabaseProvider databaseProvider; - private static @MonotonicNonNull File downloadDirectory; - private static @MonotonicNonNull Cache downloadCache; - private static @MonotonicNonNull DownloadManager downloadManager; - private static @MonotonicNonNull DownloadTracker downloadTracker; - private static @MonotonicNonNull DownloadNotificationHelper downloadNotificationHelper; - - /** Returns whether extension renderers should be used. */ + private static @MonotonicNonNull + DatabaseProvider databaseProvider; + private static @MonotonicNonNull + File downloadDirectory; + private static @MonotonicNonNull + Cache downloadCache; + private static @MonotonicNonNull + DownloadManager downloadManager; + private static @MonotonicNonNull + DownloadTracker downloadTracker; + private static @MonotonicNonNull + DownloadNotificationHelper downloadNotificationHelper; + + /** + * Returns whether extension renderers should be used. + */ public static boolean useExtensionRenderers() { // return BuildConfig.USE_DECODER_EXTENSIONS; return false; @@ -70,8 +80,8 @@ public static RenderersFactory buildRenderersFactory( int extensionRendererMode = useExtensionRenderers() ? (preferExtensionRenderer - ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) + ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; return new DefaultRenderersFactory(context.getApplicationContext()) .setExtensionRendererMode(extensionRendererMode); @@ -87,7 +97,9 @@ public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Conte return httpDataSourceFactory; } - /** Returns a {@link DataSource.Factory}. */ + /** + * Returns a {@link DataSource.Factory}. + */ public static synchronized DataSource.Factory getDataSourceFactory(Context context) { if (dataSourceFactory == null) { context = context.getApplicationContext(); @@ -193,5 +205,6 @@ private static CacheDataSource.Factory buildReadOnlyCacheDataSource( .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR); } - private DemoUtil() {} + private DemoUtil() { + } } diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java index 07f4dd2f..5f75ddf5 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -53,13 +53,19 @@ import java.util.HashMap; import java.util.concurrent.CopyOnWriteArraySet; -/** Tracks media that has been downloaded. */ +/** + * Tracks media that has been downloaded. + */ public class DownloadTracker { - /** Listens for changes in the tracked downloads. */ + /** + * Listens for changes in the tracked downloads. + */ public interface Listener { - /** Called when the tracked downloads changed. */ + /** + * Called when the tracked downloads changed. + */ void onDownloadsChanged(); } @@ -72,7 +78,8 @@ public interface Listener { private final DownloadIndex downloadIndex; private final DefaultTrackSelector.Parameters trackSelectorParameters; - @Nullable private StartDownloadDialogHelper startDownloadDialogHelper; + @Nullable + private StartDownloadDialogHelper startDownloadDialogHelper; public DownloadTracker( Context context, @@ -163,8 +170,8 @@ public void onDownloadRemoved( private final class StartDownloadDialogHelper implements DownloadHelper.Callback, - DialogInterface.OnClickListener, - DialogInterface.OnDismissListener { + DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { private final FragmentManager fragmentManager; private final DownloadHelper downloadHelper; @@ -173,7 +180,8 @@ private final class StartDownloadDialogHelper private TrackSelectionDialog trackSelectionDialog; private MappedTrackInfo mappedTrackInfo; private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask; - @Nullable private byte[] keySetId; + @Nullable + private byte[] keySetId; public StartDownloadDialogHelper( FragmentManager fragmentManager, DownloadHelper downloadHelper, MediaItem mediaItem) { @@ -368,7 +376,9 @@ private DownloadRequest buildDownloadRequest() { } } - /** Downloads a Widevine offline license in a background thread. */ + /** + * Downloads a Widevine offline license in a background thread. + */ @RequiresApi(18) private static final class WidevineOfflineLicenseFetchTask extends AsyncTask { @@ -378,8 +388,10 @@ private static final class WidevineOfflineLicenseFetchTask extends AsyncTask createMediaItemsFromIntent(Intent intent) { List mediaItems = new ArrayList<>(); if (ACTION_VIEW_LIST.equals(intent.getAction())) { @@ -82,7 +86,9 @@ public static List createMediaItemsFromIntent(Intent intent) { return mediaItems; } - /** Populates the intent with the given list of {@link MediaItem media items}. */ + /** + * Populates the intent with the given list of {@link MediaItem media items}. + */ public static void addToIntent(List mediaItems, Intent intent) { Assertions.checkArgument(!mediaItems.isEmpty()); if (mediaItems.size() == 1) { diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java index af6e3349..5407341c 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -65,7 +65,6 @@ import com.mux.stats.sdk.core.MuxSDKViewOrientation; import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; -import com.mux.stats.sdk.muxstats.AdsImaSDKListener; import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; import java.net.CookieHandler; import java.net.CookieManager; @@ -74,7 +73,9 @@ import java.util.Collections; import java.util.List; -/** An activity that plays media using {@link SimpleExoPlayer}. */ +/** + * An activity that plays media using {@link SimpleExoPlayer}. + */ public class PlayerActivity extends AppCompatActivity implements OnClickListener, PlaybackPreparer, StyledPlayerControlView.VisibilityListener { @@ -297,7 +298,9 @@ protected void setContentView() { setContentView(R.layout.player_activity); } - /** @return Whether initialization was successful. */ + /** + * @return Whether initialization was successful. + */ protected boolean initializePlayer() { if (player == null) { Intent intent = getIntent(); @@ -338,7 +341,7 @@ protected boolean initializePlayer() { CustomerVideoData customerVideoData = new CustomerVideoData(); customerVideoData.setVideoTitle(intent.getStringExtra(VIDEO_TITLE_EXTRA)); muxStats = new MuxStatsExoPlayer( - this, player, "demo-player", customerPlayerData, customerVideoData); + this, player, "demo-player", customerPlayerData, customerVideoData); Point size = new Point(); getWindowManager().getDefaultDisplay().getSize(size); muxStats.setScreenSize(size.x, size.y); @@ -413,14 +416,14 @@ private AdsLoader getAdsLoader(Uri adTagUri) { // The ads loader is reused for multiple playbacks, so that ad playback can resume. if (adsLoader == null) { adsLoader = new ImaAdsLoader.Builder(/* context= */ this) - /* - * This replaces `monitorImaAdsLoader` method because in r2.12.x ImaAdsLoader - * will create google.v3.AdsLoader on adRequest, which means that monitorImaAdsLoader - * Will always receive null pointer and will be unable to recieve add events. - */ - .setAdErrorListener(muxStats.getAdsImaSdkListener()) - .setAdEventListener(muxStats.getAdsImaSdkListener()) - .build(); + /* + * This replaces `monitorImaAdsLoader` method because in r2.12.x ImaAdsLoader + * will create google.v3.AdsLoader on adRequest, which means that monitorImaAdsLoader + * Will always receive null pointer and will be unable to recieve add events. + */ + .setAdErrorListener(muxStats.getAdsImaSdkListener()) + .setAdEventListener(muxStats.getAdsImaSdkListener()) + .build(); } adsLoader.setPlayer(player); return adsLoader; diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index ea5b38ce..e1b3e5ce 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.AssetManager; import android.net.Uri; import android.os.AsyncTask; @@ -66,7 +65,9 @@ import java.util.List; import java.util.Map; -/** An activity for selecting from a list of media samples. */ +/** + * An activity for selecting from a list of media samples. + */ public class SampleChooserActivity extends AppCompatActivity implements DownloadTracker.Listener, OnChildClickListener { @@ -94,7 +95,7 @@ public void onCreate(Bundle savedInstanceState) { Intent intent = getIntent(); String dataUri = intent.getDataString(); if (dataUri != null) { - uris = new String[] {dataUri}; + uris = new String[]{dataUri}; } else { ArrayList uriList = new ArrayList<>(); AssetManager assetManager = getAssets(); diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java index d3f9b388..100aa2bb 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java @@ -44,7 +44,9 @@ import java.util.Collections; import java.util.List; -/** Dialog to select tracks. */ +/** + * Dialog to select tracks. + */ public final class TrackSelectionDialog extends DialogFragment { private final SparseArray tabFragments; @@ -80,9 +82,9 @@ public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) { * Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be * automatically updated when tracks are selected. * - * @param trackSelector The {@link DefaultTrackSelector}. + * @param trackSelector The {@link DefaultTrackSelector}. * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is - * dismissed. + * dismissed. */ public static TrackSelectionDialog createForTrackSelector( DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) { @@ -122,16 +124,17 @@ public static TrackSelectionDialog createForTrackSelector( /** * Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}. * - * @param titleId The resource id of the dialog title. - * @param mappedTrackInfo The {@link MappedTrackInfo} to display. - * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial - * track selection. + * @param titleId The resource id of the dialog title. + * @param mappedTrackInfo The {@link MappedTrackInfo} to display. + * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the + * initial track selection. * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track) - * can be made. - * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. - * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected. - * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is - * dismissed. + * can be made. + * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. + * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are + * selected. + * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog + * is dismissed. */ public static TrackSelectionDialog createForMappedTrackInfoAndParameters( int titleId, @@ -306,7 +309,9 @@ public CharSequence getPageTitle(int position) { } } - /** Fragment to show a track selection in tab of the track selection dialog. */ + /** + * Fragment to show a track selection in tab of the track selection dialog. + */ public static final class TrackSelectionViewFragment extends Fragment implements TrackSelectionView.TrackSelectionListener { diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 26a60d33..03ee33e0 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -59,9 +59,9 @@ /** * DataSource without intermediate buffer based on Cronet API set using UrlRequest. * - *

Note: HTTP request headers will be set using all parameters passed via (in order of decreasing - * priority) the {@code dataSpec}, {@link #setRequestProperty} and the default parameters used to - * construct the instance. + *

Note: HTTP request headers will be set using all parameters passed via (in order of + * decreasing priority) the {@code dataSpec}, {@link #setRequestProperty} and the default parameters + * used to construct the instance. */ public class CronetDataSource extends BaseDataSource implements HttpDataSource { @@ -119,12 +119,14 @@ public OpenException(String errorMessage, DataSpec dataSpec, int cronetConnectio private final int readTimeoutMs; private final boolean resetTimeoutOnRedirects; private final boolean handleSetCookieRequests; - @Nullable private final RequestProperties defaultRequestProperties; + @Nullable + private final RequestProperties defaultRequestProperties; private final RequestProperties requestProperties; private final ConditionVariable operation; private final Clock clock; - @Nullable private Predicate contentTypePredicate; + @Nullable + private Predicate contentTypePredicate; // Accessed by the calling thread only. private boolean opened; @@ -133,18 +135,23 @@ public OpenException(String errorMessage, DataSpec dataSpec, int cronetConnectio // Written from the calling thread only. currentUrlRequest.start() calls ensure writes are visible // to reads made by the Cronet thread. - @Nullable private UrlRequest currentUrlRequest; - @Nullable private DataSpec currentDataSpec; + @Nullable + private UrlRequest currentUrlRequest; + @Nullable + private DataSpec currentDataSpec; // Reference written and read by calling thread only. Passed to Cronet thread as a local variable. // operation.open() calls ensure writes into the buffer are visible to reads made by the calling // thread. - @Nullable private ByteBuffer readBuffer; + @Nullable + private ByteBuffer readBuffer; // Written from the Cronet thread only. operation.open() calls ensure writes are visible to reads // made by the calling thread. - @Nullable private UrlResponseInfo responseInfo; - @Nullable private IOException exception; + @Nullable + private UrlResponseInfo responseInfo; + @Nullable + private IOException exception; private boolean finished; private volatile long currentConnectTimeoutMs; @@ -153,11 +160,12 @@ public OpenException(String errorMessage, DataSpec dataSpec, int cronetConnectio * Creates an instance. * * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. + * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This + * may be a direct executor (i.e. executes tasks on the calling thread) in + * order to avoid a thread hop from Cronet's internal network thread to the + * response handling thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling is a fast + * operation when using a direct executor. */ public CronetDataSource(CronetEngine cronetEngine, Executor executor) { this( @@ -172,17 +180,19 @@ public CronetDataSource(CronetEngine cronetEngine, Executor executor) { /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. - * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks + * on the calling thread) in order to avoid a thread hop from + * Cronet's internal network thread to the response handling + * thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling + * is a fast operation when using a direct executor. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. + * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param defaultRequestProperties Optional default {@link RequestProperties} to be sent to the - * server as HTTP headers on every request. + * server as HTTP headers on every request. */ public CronetDataSource( CronetEngine cronetEngine, @@ -205,19 +215,21 @@ public CronetDataSource( /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. - * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks + * on the calling thread) in order to avoid a thread hop from + * Cronet's internal network thread to the response handling + * thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling + * is a fast operation when using a direct executor. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. + * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param defaultRequestProperties Optional default {@link RequestProperties} to be sent to the - * server as HTTP headers on every request. - * @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded to - * the redirect url in the "Cookie" header. + * server as HTTP headers on every request. + * @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded + * to the redirect url in the "Cookie" header. */ public CronetDataSource( CronetEngine cronetEngine, @@ -241,17 +253,19 @@ public CronetDataSource( /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks on + * the calling thread) in order to avoid a thread hop from Cronet's + * internal network thread to the response handling thread. However, + * to avoid slowing down overall network performance, care must be + * taken to make sure response handling is a fast operation when using + * a direct executor. * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the - * predicate then an {@link InvalidContentTypeException} is thrown from {@link - * #open(DataSpec)}. + * predicate then an {@link InvalidContentTypeException} is thrown + * from {@link #open(DataSpec)}. * @deprecated Use {@link #CronetDataSource(CronetEngine, Executor)} and {@link - * #setContentTypePredicate(Predicate)}. + * #setContentTypePredicate(Predicate)}. */ @SuppressWarnings("deprecation") @Deprecated @@ -272,22 +286,24 @@ public CronetDataSource( /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. - * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the - * predicate then an {@link InvalidContentTypeException} is thrown from {@link - * #open(DataSpec)}. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. - * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks + * on the calling thread) in order to avoid a thread hop from + * Cronet's internal network thread to the response handling + * thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling + * is a fast operation when using a direct executor. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by + * the predicate then an {@link InvalidContentTypeException} is + * thrown from {@link #open(DataSpec)}. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. + * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param defaultRequestProperties Optional default {@link RequestProperties} to be sent to the - * server as HTTP headers on every request. + * server as HTTP headers on every request. * @deprecated Use {@link #CronetDataSource(CronetEngine, Executor, int, int, boolean, - * RequestProperties)} and {@link #setContentTypePredicate(Predicate)}. + * RequestProperties)} and {@link #setContentTypePredicate(Predicate)}. */ @SuppressWarnings("deprecation") @Deprecated @@ -313,24 +329,26 @@ public CronetDataSource( /** * Creates an instance. * - * @param cronetEngine A CronetEngine. - * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may - * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread - * hop from Cronet's internal network thread to the response handling thread. However, to - * avoid slowing down overall network performance, care must be taken to make sure response - * handling is a fast operation when using a direct executor. - * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the - * predicate then an {@link InvalidContentTypeException} is thrown from {@link - * #open(DataSpec)}. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. - * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. + * @param cronetEngine A CronetEngine. + * @param executor The {@link java.util.concurrent.Executor} that will handle + * responses. This may be a direct executor (i.e. executes tasks + * on the calling thread) in order to avoid a thread hop from + * Cronet's internal network thread to the response handling + * thread. However, to avoid slowing down overall network + * performance, care must be taken to make sure response handling + * is a fast operation when using a direct executor. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by + * the predicate then an {@link InvalidContentTypeException} is + * thrown from {@link #open(DataSpec)}. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. + * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param defaultRequestProperties Optional default {@link RequestProperties} to be sent to the - * server as HTTP headers on every request. - * @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded to - * the redirect url in the "Cookie" header. + * server as HTTP headers on every request. + * @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded + * to the redirect url in the "Cookie" header. * @deprecated Use {@link #CronetDataSource(CronetEngine, Executor, int, int, boolean, - * RequestProperties, boolean)} and {@link #setContentTypePredicate(Predicate)}. + * RequestProperties, boolean)} and {@link #setContentTypePredicate(Predicate)}. */ @Deprecated public CronetDataSource( @@ -382,7 +400,7 @@ public CronetDataSource( * {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link #open(DataSpec)}. * * @param contentTypePredicate The content type {@link Predicate}, or {@code null} to clear a - * predicate that was previously set. + * predicate that was previously set. */ public void setContentTypePredicate(@Nullable Predicate contentTypePredicate) { this.contentTypePredicate = contentTypePredicate; @@ -567,24 +585,24 @@ public int read(byte[] buffer, int offset, int readLength) throws HttpDataSource * starting at {@code buffer.position()}. Advances the position of the buffer by the number of * bytes read and returns this length. * - *

If there is an error, a {@link HttpDataSourceException} is thrown and the contents of {@code - * buffer} should be ignored. If the exception has error code {@code + *

If there is an error, a {@link HttpDataSourceException} is thrown and the contents of + * {@code buffer} should be ignored. If the exception has error code {@code * HttpDataSourceException.TYPE_READ}, note that Cronet may continue writing into {@code buffer} * after the method has returned. Thus the caller should not attempt to reuse the buffer. * - *

If {@code buffer.remaining()} is zero then 0 is returned. Otherwise, if no data is available - * because the end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is - * returned. Otherwise, the call will block until at least one byte of data has been read and the - * number of bytes read is returned. + *

If {@code buffer.remaining()} is zero then 0 is returned. Otherwise, if no data is + * available because the end of the opened range has been reached, then {@link + * C#RESULT_END_OF_INPUT} is returned. Otherwise, the call will block until at least one byte of + * data has been read and the number of bytes read is returned. * *

Passed buffer must be direct ByteBuffer. If you have a non-direct ByteBuffer, consider the * alternative read method with its backed array. * * @param buffer The ByteBuffer into which the read data should be stored. Must be a direct - * ByteBuffer. + * ByteBuffer. * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if no data is available - * because the end of the opened range has been reached. - * @throws HttpDataSourceException If an error occurs reading from the source. + * because the end of the opened range has been reached. + * @throws HttpDataSourceException If an error occurs reading from the source. * @throws IllegalArgumentException If {@code buffer} is not a direct ByteBuffer. */ public int read(ByteBuffer buffer) throws HttpDataSourceException { @@ -687,13 +705,17 @@ public synchronized void close() { } } - /** Returns current {@link UrlRequest}. May be null if the data source is not opened. */ + /** + * Returns current {@link UrlRequest}. May be null if the data source is not opened. + */ @Nullable protected UrlRequest getCurrentUrlRequest() { return currentUrlRequest; } - /** Returns current {@link UrlResponseInfo}. May be null if the data source is not opened. */ + /** + * Returns current {@link UrlResponseInfo}. May be null if the data source is not opened. + */ @Nullable protected UrlResponseInfo getCurrentUrlResponseInfo() { return responseInfo; @@ -1014,7 +1036,7 @@ public synchronized void onFailed( } if (error instanceof NetworkException && ((NetworkException) error).getErrorCode() - == NetworkException.ERROR_HOSTNAME_NOT_RESOLVED) { + == NetworkException.ERROR_HOSTNAME_NOT_RESOLVED) { exception = new UnknownHostException(); } else { exception = error; diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java index 85c9d09a..1fdfa691 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java @@ -45,7 +45,8 @@ public final class CronetDataSourceFactory extends BaseFactory { private final CronetEngineWrapper cronetEngineWrapper; private final Executor executor; - @Nullable private final TransferListener transferListener; + @Nullable + private final TransferListener transferListener; private final int connectTimeoutMs; private final int readTimeoutMs; private final boolean resetTimeoutOnRedirects; @@ -61,9 +62,10 @@ public final class CronetDataSourceFactory extends BaseFactory { * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case no - * suitable CronetEngine can be build. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case + * no suitable CronetEngine can be build. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -89,7 +91,8 @@ public CronetDataSourceFactory( * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. */ public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper, Executor executor) { this(cronetEngineWrapper, executor, DEFAULT_USER_AGENT); @@ -105,8 +108,9 @@ public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper, Executor * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param userAgent A user agent used to create a fallback HttpDataSource if needed. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param userAgent A user agent used to create a fallback HttpDataSource if needed. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, Executor executor, String userAgent) { @@ -131,12 +135,14 @@ public CronetDataSourceFactory( *

If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link * DefaultHttpDataSourceFactory} will be used instead. * - * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. + * @param cronetEngineWrapper A {@link CronetEngineWrapper}. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. - * @param userAgent A user agent used to create a fallback HttpDataSource if needed. + * @param userAgent A user agent used to create a fallback HttpDataSource if + * needed. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -166,13 +172,14 @@ public CronetDataSourceFactory( *

If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, the provided * fallback {@link HttpDataSource.Factory} will be used instead. * - * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. + * @param cronetEngineWrapper A {@link CronetEngineWrapper}. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. - * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case no - * suitable CronetEngine can be build. + * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in + * case no suitable CronetEngine can be build. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -201,10 +208,11 @@ public CronetDataSourceFactory( * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. - * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case no - * suitable CronetEngine can be build. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. + * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case + * no suitable CronetEngine can be build. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -231,8 +239,9 @@ public CronetDataSourceFactory( * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -251,9 +260,10 @@ public CronetDataSourceFactory( * {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout. * * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. - * @param userAgent A user agent used to create a fallback HttpDataSource if needed. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. + * @param userAgent A user agent used to create a fallback HttpDataSource if needed. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -281,13 +291,15 @@ public CronetDataSourceFactory( *

If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link * DefaultHttpDataSourceFactory} will be used instead. * - * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. + * @param cronetEngineWrapper A {@link CronetEngineWrapper}. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. - * @param userAgent A user agent used to create a fallback HttpDataSource if needed. + * @param userAgent A user agent used to create a fallback HttpDataSource if + * needed. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, @@ -314,14 +326,15 @@ public CronetDataSourceFactory( *

If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, the provided * fallback {@link HttpDataSource.Factory} will be used instead. * - * @param cronetEngineWrapper A {@link CronetEngineWrapper}. - * @param executor The {@link java.util.concurrent.Executor} that will perform the requests. - * @param transferListener An optional listener. - * @param connectTimeoutMs The connection timeout, in milliseconds. - * @param readTimeoutMs The read timeout, in milliseconds. + * @param cronetEngineWrapper A {@link CronetEngineWrapper}. + * @param executor The {@link java.util.concurrent.Executor} that will perform the + * requests. + * @param transferListener An optional listener. + * @param connectTimeoutMs The connection timeout, in milliseconds. + * @param readTimeoutMs The read timeout, in milliseconds. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. - * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case no - * suitable CronetEngine can be build. + * @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in + * case no suitable CronetEngine can be build. */ public CronetDataSourceFactory( CronetEngineWrapper cronetEngineWrapper, diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java index 9f709b14..e1314a06 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java @@ -40,8 +40,10 @@ public final class CronetEngineWrapper { private static final String TAG = "CronetEngineWrapper"; - @Nullable private final CronetEngine cronetEngine; - @CronetEngineSource private final int cronetEngineSource; + @Nullable + private final CronetEngine cronetEngine; + @CronetEngineSource + private final int cronetEngineSource; /** * Source of {@link CronetEngine}. One of {@link #SOURCE_NATIVE}, {@link #SOURCE_GMS}, {@link @@ -50,7 +52,10 @@ public final class CronetEngineWrapper { @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({SOURCE_NATIVE, SOURCE_GMS, SOURCE_UNKNOWN, SOURCE_USER_PROVIDED, SOURCE_UNAVAILABLE}) - public @interface CronetEngineSource {} + public @interface CronetEngineSource { + + } + /** * Natively bundled Cronet implementation. */ @@ -74,8 +79,8 @@ public final class CronetEngineWrapper { /** * Creates a wrapper for a {@link CronetEngine} which automatically selects the most suitable - * {@link CronetProvider}. Sets wrapper to prefer natively bundled Cronet over GMSCore Cronet - * if both are available. + * {@link CronetProvider}. Sets wrapper to prefer natively bundled Cronet over GMSCore Cronet if + * both are available. * * @param context A context. */ @@ -87,9 +92,9 @@ public CronetEngineWrapper(Context context) { * Creates a wrapper for a {@link CronetEngine} which automatically selects the most suitable * {@link CronetProvider} based on user preference. * - * @param context A context. + * @param context A context. * @param preferGMSCoreCronet Whether Cronet from GMSCore should be preferred over natively - * bundled Cronet if both are available. + * bundled Cronet if both are available. */ public CronetEngineWrapper(Context context, boolean preferGMSCoreCronet) { CronetEngine cronetEngine = null; @@ -164,7 +169,8 @@ public int getCronetEngineSource() { private static class CronetProviderComparator implements Comparator { - @Nullable private final String gmsCoreCronetName; + @Nullable + private final String gmsCoreCronetName; private final boolean preferGMSCoreCronet; // Multi-catch can only be used for API 19+ in this case. @@ -209,8 +215,7 @@ public boolean isGMSCoreProvider(String providerName) { } /** - * Convert Cronet provider name into a sortable preference value. - * Smaller values are preferred. + * Convert Cronet provider name into a sortable preference value. Smaller values are preferred. */ private int evaluateCronetProviderType(String providerName) { if (isNativeProvider(providerName)) { diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 4185a158..d79153d8 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -115,7 +115,9 @@ public final class ImaAdsLoader ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); } - /** Builder for {@link ImaAdsLoader}. */ + /** + * Builder for {@link ImaAdsLoader}. + */ public static final class Builder { /** @@ -131,13 +133,20 @@ public static final class Builder { private final Context context; - @Nullable private ImaSdkSettings imaSdkSettings; - @Nullable private AdErrorListener adErrorListener; - @Nullable private AdEventListener adEventListener; - @Nullable private VideoAdPlayer.VideoAdPlayerCallback videoAdPlayerCallback; - @Nullable private List adMediaMimeTypes; - @Nullable private Set adUiElements; - @Nullable private Collection companionAdSlots; + @Nullable + private ImaSdkSettings imaSdkSettings; + @Nullable + private AdErrorListener adErrorListener; + @Nullable + private AdEventListener adEventListener; + @Nullable + private VideoAdPlayer.VideoAdPlayerCallback videoAdPlayerCallback; + @Nullable + private List adMediaMimeTypes; + @Nullable + private Set adUiElements; + @Nullable + private Collection companionAdSlots; private long adPreloadTimeoutMs; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; @@ -178,9 +187,8 @@ public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { } /** - * Sets a listener for ad errors that will be passed to {@link - * AdsLoader#addAdErrorListener(AdErrorListener)} and {@link - * AdsManager#addAdErrorListener(AdErrorListener)}. + * Sets a listener for ad errors that will be passed to {@link AdsLoader#addAdErrorListener(AdErrorListener)} + * and {@link AdsManager#addAdErrorListener(AdErrorListener)}. * * @param adErrorListener The ad error listener. * @return This builder, for convenience. @@ -191,8 +199,7 @@ public Builder setAdErrorListener(AdErrorListener adErrorListener) { } /** - * Sets a listener for ad events that will be passed to {@link - * AdsManager#addAdEventListener(AdEventListener)}. + * Sets a listener for ad events that will be passed to {@link AdsManager#addAdEventListener(AdEventListener)}. * * @param adEventListener The ad event listener. * @return This builder, for convenience. @@ -248,9 +255,10 @@ public Builder setCompanionAdSlots(Collection companionAdSlots) * AdsMediaSource} will be used. * * @param adMediaMimeTypes The MIME types to prioritize for linear ad media. May contain {@link - * MimeTypes#APPLICATION_MPD}, {@link MimeTypes#APPLICATION_M3U8}, {@link - * MimeTypes#VIDEO_MP4}, {@link MimeTypes#VIDEO_WEBM}, {@link MimeTypes#VIDEO_H263}, {@link - * MimeTypes#AUDIO_MP4} and {@link MimeTypes#AUDIO_MPEG}. + * MimeTypes#APPLICATION_MPD}, {@link MimeTypes#APPLICATION_M3U8}, + * {@link MimeTypes#VIDEO_MP4}, {@link MimeTypes#VIDEO_WEBM}, {@link + * MimeTypes#VIDEO_H263}, {@link MimeTypes#AUDIO_MP4} and {@link + * MimeTypes#AUDIO_MPEG}. * @return This builder, for convenience. * @see AdsRenderingSettings#setMimeTypes(List) */ @@ -265,11 +273,11 @@ public Builder setAdMediaMimeTypes(List adMediaMimeTypes) { * C#TIME_UNSET} if there should be no such timeout. The default value is {@value * #DEFAULT_AD_PRELOAD_TIMEOUT_MS} ms. * - *

The purpose of this timeout is to avoid playback getting stuck in the unexpected case that - * the IMA SDK does not load an ad break based on the player's reported content position. + *

The purpose of this timeout is to avoid playback getting stuck in the unexpected case + * that the IMA SDK does not load an ad break based on the player's reported content position. * * @param adPreloadTimeoutMs The timeout buffering duration in milliseconds, or {@link - * C#TIME_UNSET} for no timeout. + * C#TIME_UNSET} for no timeout. * @return This builder, for convenience. */ public Builder setAdPreloadTimeoutMs(long adPreloadTimeoutMs) { @@ -322,7 +330,7 @@ public Builder setMaxMediaBitrate(int bitrate) { * setting is {@code true}. * * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on - * Android TV devices. + * Android TV devices. * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ @@ -338,7 +346,7 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila * setting is {@code true}. * * @param playAdBeforeStartPosition Whether to play an ad before the start position when - * beginning playback. + * beginning playback. * @return This builder, for convenience. */ public Builder setPlayAdBeforeStartPosition(boolean playAdBeforeStartPosition) { @@ -352,7 +360,7 @@ public Builder setPlayAdBeforeStartPosition(boolean playAdBeforeStartPosition) { * enabled in production applications. * * @param debugModeEnabled Whether to enable outputting verbose logs for the IMA extension and - * IMA SDK. + * IMA SDK. * @return This builder, for convenience. * @see ImaSdkSettings#setDebugMode(boolean) */ @@ -362,7 +370,7 @@ public Builder setDebugModeEnabled(boolean debugModeEnabled) { } @VisibleForTesting - /* package */ Builder setImaFactory(ImaUtil.ImaFactory imaFactory) { + /* package */ Builder setImaFactory(ImaUtil.ImaFactory imaFactory) { this.imaFactory = checkNotNull(imaFactory); return this; } @@ -370,13 +378,12 @@ public Builder setDebugModeEnabled(boolean debugModeEnabled) { /** * Returns a new {@link ImaAdsLoader} for the specified ad tag. * - * @param adTagUri The URI of a compatible ad tag to load. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * information on compatible ad tags. + * @param adTagUri The URI of a compatible ad tag to load. See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for information on compatible ad tags. * @return The new {@link ImaAdsLoader}. * @deprecated Pass the ad tag URI when setting media item playback properties (if using the - * media item API) or as a {@link DataSpec} when constructing the {@link AdsMediaSource} (if - * using media sources directly). + * media item API) or as a {@link DataSpec} when constructing the {@link AdsMediaSource} (if + * using media sources directly). */ @Deprecated public ImaAdsLoader buildForAdTag(Uri adTagUri) { @@ -392,13 +399,13 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) { * Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response. * * @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of - * making a request via an ad tag URL. + * making a request via an ad tag URL. * @return The new {@link ImaAdsLoader}. * @deprecated Pass the ads response as a data URI when setting media item playback properties - * (if using the media item API) or as a {@link DataSpec} when constructing the {@link - * AdsMediaSource} (if using media sources directly). {@link - * Util#getDataUriForString(String, String)} can be used to construct a data URI from - * literal string ads response (with MIME type text/xml). + * (if using the media item API) or as a {@link DataSpec} when constructing the {@link + * AdsMediaSource} (if using media sources directly). {@link Util#getDataUriForString(String, + * String)} can be used to construct a data URI from literal string ads response (with MIME type + * text/xml). */ @Deprecated public ImaAdsLoader buildForAdsResponse(String adsResponse) { @@ -406,7 +413,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { context, getConfiguration(), imaFactory, /* adTagUri= */ null, adsResponse); } - /** Returns a new {@link ImaAdsLoader}. */ + /** + * Returns a new {@link ImaAdsLoader}. + */ public ImaAdsLoader build() { return new ImaAdsLoader( context, getConfiguration(), imaFactory, /* adTagUri= */ null, /* adsResponse= */ null); @@ -445,7 +454,9 @@ public ImaAdsLoader build() { */ private static final int AD_PROGRESS_UPDATE_INTERVAL_MS = 100; - /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ + /** + * The value used in {@link VideoProgressUpdate}s to indicate an unset duration. + */ private static final long IMA_DURATION_UNSET = -1L; /** @@ -458,18 +469,27 @@ public ImaAdsLoader build() { * milliseconds. */ private static final long THRESHOLD_AD_PRELOAD_MS = 4000; - /** The threshold below which ad cue points are treated as matching, in microseconds. */ + /** + * The threshold below which ad cue points are treated as matching, in microseconds. + */ private static final long THRESHOLD_AD_MATCH_US = 1000; private static final int TIMEOUT_UNSET = -1; private static final int BITRATE_UNSET = -1; - /** The state of ad playback. */ + /** + * The state of ad playback. + */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) - private @interface ImaAdState {} - /** The ad playback state when IMA is not playing an ad. */ + private @interface ImaAdState { + + } + + /** + * The ad playback state when IMA is not playing an ad. + */ private static final int IMA_AD_STATE_NONE = 0; /** * The ad playback state when IMA has called {@link ComponentListener#playAd(AdMediaInfo)} and not @@ -487,8 +507,10 @@ public ImaAdsLoader build() { private final ImaUtil.Configuration configuration; private final Context context; private final ImaUtil.ImaFactory imaFactory; - @Nullable private final Uri adTagUri; - @Nullable private final String adsResponse; + @Nullable + private final Uri adTagUri; + @Nullable + private final String adsResponse; private final ImaSdkSettings imaSdkSettings; private final Timeline.Period period; private final Handler handler; @@ -497,45 +519,70 @@ public ImaAdsLoader build() { private final Runnable updateAdProgressRunnable; private final BiMap adInfoByAdMediaInfo; - private @MonotonicNonNull AdDisplayContainer adDisplayContainer; - private @MonotonicNonNull AdsLoader adsLoader; + private @MonotonicNonNull + AdDisplayContainer adDisplayContainer; + private @MonotonicNonNull + AdsLoader adsLoader; private boolean wasSetPlayerCalled; - @Nullable private Player nextPlayer; - @Nullable private Object pendingAdRequestContext; + @Nullable + private Player nextPlayer; + @Nullable + private Object pendingAdRequestContext; private List supportedMimeTypes; - @Nullable private EventListener eventListener; - @Nullable private Player player; + @Nullable + private EventListener eventListener; + @Nullable + private Player player; private DataSpec adTagDataSpec; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercent; - @Nullable private AdsManager adsManager; + @Nullable + private AdsManager adsManager; private boolean isAdsManagerInitialized; private boolean hasAdPlaybackState; - @Nullable private AdLoadException pendingAdLoadError; + @Nullable + private AdLoadException pendingAdLoadError; private Timeline timeline; private long contentDurationMs; private AdPlaybackState adPlaybackState; // Fields tracking IMA's state. - /** Whether IMA has sent an ad event to pause content since the last resume content event. */ + /** + * Whether IMA has sent an ad event to pause content since the last resume content event. + */ private boolean imaPausedContent; - /** The current ad playback state. */ - private @ImaAdState int imaAdState; - /** The current ad media info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. */ - @Nullable private AdMediaInfo imaAdMediaInfo; - /** The current ad info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. */ - @Nullable private AdInfo imaAdInfo; - /** Whether IMA has been notified that playback of content has finished. */ + /** + * The current ad playback state. + */ + private @ImaAdState + int imaAdState; + /** + * The current ad media info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. + */ + @Nullable + private AdMediaInfo imaAdMediaInfo; + /** + * The current ad info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. + */ + @Nullable + private AdInfo imaAdInfo; + /** + * Whether IMA has been notified that playback of content has finished. + */ private boolean sentContentComplete; // Fields tracking the player/loader state. - /** Whether the player is playing an ad. */ + /** + * Whether the player is playing an ad. + */ private boolean playingAd; - /** Whether the player is buffering an ad. */ + /** + * Whether the player is buffering an ad. + */ private boolean bufferingAd; /** * If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET} @@ -546,7 +593,8 @@ public ImaAdsLoader build() { * The ad info for a pending ad for which the media failed preparation, or {@code null} if no * pending ads have failed to prepare. */ - @Nullable private AdInfo pendingAdPrepareErrorAdInfo; + @Nullable + private AdInfo pendingAdPrepareErrorAdInfo; /** * If a content period has finished but IMA has not yet called {@link * ComponentListener#playAd(AdMediaInfo)}, stores the value of {@link @@ -559,7 +607,9 @@ public ImaAdsLoader build() { * content progress should increase. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressOffsetMs; - /** Stores the pending content position when a seek operation was intercepted to play an ad. */ + /** + * Stores the pending content position when a seek operation was intercepted to play an ad. + */ private long pendingContentPositionMs; /** * Whether {@link ComponentListener#getContentProgress()} has sent {@link @@ -577,13 +627,13 @@ public ImaAdsLoader build() { * *

If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead. * - * @param context The context. + * @param context The context. * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. * @deprecated Use {@link Builder} to create an instance. Pass the ad tag URI when setting media - * item playback properties (if using the media item API) or as a {@link DataSpec} when - * constructing the {@link AdsMediaSource} (if using media sources directly). + * item playback properties (if using the media item API) or as a {@link DataSpec} when + * constructing the {@link AdsMediaSource} (if using media sources directly). */ @Deprecated public ImaAdsLoader(Context context, Uri adTagUri) { @@ -672,10 +722,10 @@ public AdDisplayContainer getAdDisplayContainer() { * the player. * * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code - * null} if playing audio-only ads. + * null} if playing audio-only ads. * @deprecated Use {@link #requestAds(DataSpec, ViewGroup)}, specifying the ad tag data spec to - * request, and migrate off deprecated builder methods/constructor that require an ad tag or - * ads response. + * request, and migrate off deprecated builder methods/constructor that require an ad tag or ads + * response. */ @Deprecated public void requestAds(@Nullable ViewGroup adViewGroup) { @@ -690,9 +740,9 @@ public void requestAds(@Nullable ViewGroup adViewGroup) { * the player. * * @param adTagDataSpec The data specification of the ad tag to load. See class javadoc for - * information about compatible ad tag formats. - * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code - * null} if playing audio-only ads. + * information about compatible ad tag formats. + * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or + * {@code null} if playing audio-only ads. */ public void requestAds(DataSpec adTagDataSpec, @Nullable ViewGroup adViewGroup) { if (hasAdPlaybackState || adsManager != null || pendingAdRequestContext != null) { @@ -1529,8 +1579,8 @@ private void ensureSentContentCompleteIfAtEndOfStream() { && contentDurationMs != C.TIME_UNSET && pendingContentPositionMs == C.TIME_UNSET && getContentPeriodPositionMs(checkNotNull(player), timeline, period) - + THRESHOLD_END_OF_CONTENT_MS - >= contentDurationMs) { + + THRESHOLD_END_OF_CONTENT_MS + >= contentDurationMs) { sendContentComplete(); } } @@ -1634,8 +1684,8 @@ private static long getContentPeriodPositionMs( long contentWindowPositionMs = player.getContentPosition(); return contentWindowPositionMs - (timeline.isEmpty() - ? 0 - : timeline.getPeriod(/* periodIndex= */ 0, period).getPositionInWindowMs()); + ? 0 + : timeline.getPeriod(/* periodIndex= */ 0, period).getPositionInWindowMs()); } private static Looper getImaLooper() { @@ -1673,10 +1723,10 @@ private void destroyAdsManager() { private final class ComponentListener implements AdsLoadedListener, - ContentProgressProvider, - AdEventListener, - AdErrorListener, - VideoAdPlayer { + ContentProgressProvider, + AdEventListener, + AdErrorListener, + VideoAdPlayer { // AdsLoader.AdsLoadedListener implementation. @@ -1850,6 +1900,7 @@ public void release() { // TODO: Consider moving this into AdPlaybackState. private static final class AdInfo { + public final int adGroupIndex; public final int adIndexInAdGroup; @@ -1891,6 +1942,7 @@ public String toString() { * ImaSdkFactory}. */ private static final class DefaultImaFactory implements ImaUtil.ImaFactory { + @Override public ImaSdkSettings createImaSdkSettings() { return ImaSdkFactory.getInstance().createImaSdkSettings(); diff --git a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java index a4f1ec92..cf073e95 100644 --- a/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java +++ b/demo/src/r2_12_1_ads/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java @@ -45,25 +45,38 @@ import java.util.List; import java.util.Set; -/** Utilities for working with IMA SDK and IMA extension data types. */ +/** + * Utilities for working with IMA SDK and IMA extension data types. + */ /* package */ final class ImaUtil { - /** Factory for objects provided by the IMA SDK. */ + /** + * Factory for objects provided by the IMA SDK. + */ public interface ImaFactory { - /** Creates {@link ImaSdkSettings} for configuring the IMA SDK. */ + + /** + * Creates {@link ImaSdkSettings} for configuring the IMA SDK. + */ ImaSdkSettings createImaSdkSettings(); + /** * Creates {@link AdsRenderingSettings} for giving the {@link AdsManager} parameters that * control rendering of ads. */ AdsRenderingSettings createAdsRenderingSettings(); + /** * Creates an {@link AdDisplayContainer} to hold the player for video ads, a container for * non-linear ads, and slots for companion ads. */ AdDisplayContainer createAdDisplayContainer(ViewGroup container, VideoAdPlayer player); - /** Creates an {@link AdDisplayContainer} to hold the player for audio ads. */ + + /** + * Creates an {@link AdDisplayContainer} to hold the player for audio ads. + */ AdDisplayContainer createAudioAdDisplayContainer(Context context, VideoAdPlayer player); + /** * Creates a {@link FriendlyObstruction} to describe an obstruction considered "friendly" for * viewability measurement purposes. @@ -72,14 +85,22 @@ FriendlyObstruction createFriendlyObstruction( View view, FriendlyObstructionPurpose friendlyObstructionPurpose, @Nullable String reasonDetail); - /** Creates an {@link AdsRequest} to contain the data used to request ads. */ + + /** + * Creates an {@link AdsRequest} to contain the data used to request ads. + */ AdsRequest createAdsRequest(); - /** Creates an {@link AdsLoader} for requesting ads using the specified settings. */ + + /** + * Creates an {@link AdsLoader} for requesting ads using the specified settings. + */ AdsLoader createAdsLoader( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer); } - /** Stores configuration for ad loading and playback. */ + /** + * Stores configuration for ad loading and playback. + */ public static final class Configuration { public final long adPreloadTimeoutMs; @@ -88,13 +109,20 @@ public static final class Configuration { public final boolean focusSkipButtonWhenAvailable; public final boolean playAdBeforeStartPosition; public final int mediaBitrate; - @Nullable public final List adMediaMimeTypes; - @Nullable public final Set adUiElements; - @Nullable public final Collection companionAdSlots; - @Nullable public final AdErrorEvent.AdErrorListener applicationAdErrorListener; - @Nullable public final AdEvent.AdEventListener applicationAdEventListener; - @Nullable public final VideoAdPlayer.VideoAdPlayerCallback applicationVideoAdPlayerCallback; - @Nullable public final ImaSdkSettings imaSdkSettings; + @Nullable + public final List adMediaMimeTypes; + @Nullable + public final Set adUiElements; + @Nullable + public final Collection companionAdSlots; + @Nullable + public final AdErrorEvent.AdErrorListener applicationAdErrorListener; + @Nullable + public final AdEvent.AdEventListener applicationAdEventListener; + @Nullable + public final VideoAdPlayer.VideoAdPlayerCallback applicationVideoAdPlayerCallback; + @Nullable + public final ImaSdkSettings imaSdkSettings; public final boolean debugModeEnabled; public Configuration( @@ -176,7 +204,9 @@ public static AdPlaybackState getInitialAdPlaybackStateForCuePoints(List return new AdPlaybackState(adGroupTimesUs); } - /** Returns an {@link AdsRequest} based on the specified ad tag {@link DataSpec}. */ + /** + * Returns an {@link AdsRequest} based on the specified ad tag {@link DataSpec}. + */ public static AdsRequest getAdsRequestForAdTagDataSpec( ImaFactory imaFactory, DataSpec adTagDataSpec) throws IOException { AdsRequest request = imaFactory.createAdsRequest(); @@ -194,7 +224,9 @@ public static AdsRequest getAdsRequestForAdTagDataSpec( return request; } - /** Returns whether the ad error indicates that an entire ad group failed to load. */ + /** + * Returns whether the ad error indicates that an entire ad group failed to load. + */ public static boolean isAdGroupLoadError(AdError adError) { // TODO: Find out what other errors need to be handled (if any), and whether each one relates to // a single ad, ad group or the whole timeline. @@ -202,5 +234,6 @@ public static boolean isAdGroupLoadError(AdError adError) { || adError.getErrorCode() == AdError.AdErrorCode.UNKNOWN_ERROR; } - private ImaUtil() {} + private ImaUtil() { + } } diff --git a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DemoApplication.java index 757d3e4e..3d1238f4 100644 --- a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.demo; import android.app.Application; - import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; @@ -47,12 +46,12 @@ public class DemoApplication extends Application { private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads"; private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2; private static final DownloadAction.Deserializer[] DOWNLOAD_DESERIALIZERS = - new DownloadAction.Deserializer[] { - DashDownloadAction.DESERIALIZER, - HlsDownloadAction.DESERIALIZER, - SsDownloadAction.DESERIALIZER, - ProgressiveDownloadAction.DESERIALIZER - }; + new DownloadAction.Deserializer[]{ + DashDownloadAction.DESERIALIZER, + HlsDownloadAction.DESERIALIZER, + SsDownloadAction.DESERIALIZER, + ProgressiveDownloadAction.DESERIALIZER + }; protected String userAgent; @@ -67,19 +66,25 @@ public void onCreate() { userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); } - /** Returns a {@link DataSource.Factory}. */ + /** + * Returns a {@link DataSource.Factory}. + */ public DataSource.Factory buildDataSourceFactory() { DefaultDataSourceFactory upstreamFactory = - new DefaultDataSourceFactory(this, buildHttpDataSourceFactory()); + new DefaultDataSourceFactory(this, buildHttpDataSourceFactory()); return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache()); } - /** Returns a {@link HttpDataSource.Factory}. */ + /** + * Returns a {@link HttpDataSource.Factory}. + */ public HttpDataSource.Factory buildHttpDataSourceFactory() { return new DefaultHttpDataSourceFactory(userAgent); } - /** Returns whether extension renderers should be used. */ + /** + * Returns whether extension renderers should be used. + */ public boolean useExtensionRenderers() { return "withExtensions".equals(BuildConfig.FLAVOR); } @@ -97,20 +102,20 @@ public DownloadTracker getDownloadTracker() { private synchronized void initDownloadManager() { if (downloadManager == null) { DownloaderConstructorHelper downloaderConstructorHelper = - new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory()); + new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory()); downloadManager = - new DownloadManager( - downloaderConstructorHelper, - MAX_SIMULTANEOUS_DOWNLOADS, - DownloadManager.DEFAULT_MIN_RETRY_COUNT, - new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE)); + new DownloadManager( + downloaderConstructorHelper, + MAX_SIMULTANEOUS_DOWNLOADS, + DownloadManager.DEFAULT_MIN_RETRY_COUNT, + new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE)); downloadTracker = - new DownloadTracker( - /* context= */ this, - buildDataSourceFactory(), - new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE), - DOWNLOAD_DESERIALIZERS - ); + new DownloadTracker( + /* context= */ this, + buildDataSourceFactory(), + new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE), + DOWNLOAD_DESERIALIZERS + ); downloadManager.addListener(downloadTracker); } } @@ -134,13 +139,13 @@ private File getDownloadDirectory() { } private static CacheDataSourceFactory buildReadOnlyCacheDataSource( - DefaultDataSourceFactory upstreamFactory, Cache cache) { + DefaultDataSourceFactory upstreamFactory, Cache cache) { return new CacheDataSourceFactory( - cache, - upstreamFactory, - new FileDataSourceFactory(), - /* cacheWriteDataSinkFactory= */ null, - CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, - /* eventListener= */ null); + cache, + upstreamFactory, + new FileDataSourceFactory(), + /* cacheWriteDataSinkFactory= */ null, + CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, + /* eventListener= */ null); } } diff --git a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index 7d1ab16c..b5936cc3 100644 --- a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -24,7 +24,9 @@ import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.Util; -/** A service for downloading media. */ +/** + * A service for downloading media. + */ public class DemoDownloadService extends DownloadService { private static final String CHANNEL_ID = "download_channel"; diff --git a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DownloadTracker.java index 5f985c81..45ab3f43 100644 --- a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -64,10 +64,14 @@ */ public class DownloadTracker implements DownloadManager.Listener { - /** Listens for changes in the tracked downloads. */ + /** + * Listens for changes in the tracked downloads. + */ public interface Listener { - /** Called when the tracked downloads changed. */ + /** + * Called when the tracked downloads changed. + */ void onDownloadsChanged(); } @@ -82,10 +86,10 @@ public interface Listener { private final Handler actionFileWriteHandler; public DownloadTracker( - Context context, - DataSource.Factory dataSourceFactory, - File actionFile, - DownloadAction.Deserializer... deserializers) { + Context context, + DataSource.Factory dataSourceFactory, + File actionFile, + DownloadAction.Deserializer... deserializers) { this.context = context.getApplicationContext(); this.dataSourceFactory = dataSourceFactory; this.actionFile = new ActionFile(actionFile); @@ -96,7 +100,7 @@ public DownloadTracker( actionFileWriteThread.start(); actionFileWriteHandler = new Handler(actionFileWriteThread.getLooper()); loadTrackedActions( - deserializers.length > 0 ? deserializers : DownloadAction.getDefaultDeserializers()); + deserializers.length > 0 ? deserializers : DownloadAction.getDefaultDeserializers()); } public void addListener(Listener listener) { @@ -122,11 +126,11 @@ public List getOfflineStreamKeys(Uri uri) { public void toggleDownload(Activity activity, String name, Uri uri, String extension) { if (isDownloaded(uri)) { DownloadAction removeAction = - getDownloadHelper(uri, extension).getRemoveAction(Util.getUtf8Bytes(name)); + getDownloadHelper(uri, extension).getRemoveAction(Util.getUtf8Bytes(name)); startServiceWithAction(removeAction); } else { StartDownloadDialogHelper helper = - new StartDownloadDialogHelper(activity, getDownloadHelper(uri, extension), name); + new StartDownloadDialogHelper(activity, getDownloadHelper(uri, extension), name); helper.prepare(); } } @@ -143,7 +147,7 @@ public void onTaskStateChanged(DownloadManager downloadManager, TaskState taskSt DownloadAction action = taskState.action; Uri uri = action.uri; if ((action.isRemoveAction && taskState.state == TaskState.STATE_COMPLETED) - || (!action.isRemoveAction && taskState.state == TaskState.STATE_FAILED)) { + || (!action.isRemoveAction && taskState.state == TaskState.STATE_FAILED)) { // A download has been removed, or has failed. Stop tracking it. if (trackedDownloadStates.remove(uri) != null) { handleTrackedDownloadStatesChanged(); @@ -175,13 +179,13 @@ private void handleTrackedDownloadStatesChanged() { } final DownloadAction[] actions = trackedDownloadStates.values().toArray(new DownloadAction[0]); actionFileWriteHandler.post( - () -> { - try { - actionFile.store(actions); - } catch (IOException e) { - Log.e(TAG, "Failed to store tracked actions", e); - } - }); + () -> { + try { + actionFile.store(actions); + } catch (IOException e) { + Log.e(TAG, "Failed to store tracked actions", e); + } + }); } private void startDownload(DownloadAction action) { @@ -215,7 +219,7 @@ private DownloadHelper getDownloadHelper(Uri uri, String extension) { } private final class StartDownloadDialogHelper - implements DownloadHelper.Callback, DialogInterface.OnClickListener { + implements DownloadHelper.Callback, DialogInterface.OnClickListener { private final DownloadHelper downloadHelper; private final String name; @@ -227,14 +231,14 @@ private final class StartDownloadDialogHelper private final ListView representationList; public StartDownloadDialogHelper( - Activity activity, DownloadHelper downloadHelper, String name) { + Activity activity, DownloadHelper downloadHelper, String name) { this.downloadHelper = downloadHelper; this.name = name; builder = - new AlertDialog.Builder(activity) - .setTitle(R.string.exo_download_description) - .setPositiveButton(android.R.string.ok, this) - .setNegativeButton(android.R.string.cancel, null); + new AlertDialog.Builder(activity) + .setTitle(R.string.exo_download_description) + .setPositiveButton(android.R.string.ok, this) + .setNegativeButton(android.R.string.cancel, null); // Inflate with the builder's context to ensure the correct style is used. LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); @@ -242,8 +246,8 @@ public StartDownloadDialogHelper( trackKeys = new ArrayList<>(); trackTitles = - new ArrayAdapter<>( - builder.getContext(), android.R.layout.simple_list_item_multiple_choice); + new ArrayAdapter<>( + builder.getContext(), android.R.layout.simple_list_item_multiple_choice); representationList = dialogView.findViewById(R.id.representation_list); representationList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); representationList.setAdapter(trackTitles); @@ -274,8 +278,8 @@ public void onPrepared(DownloadHelper helper) { @Override public void onPrepareError(DownloadHelper helper, IOException e) { Toast.makeText( - context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG) - .show(); + context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG) + .show(); Log.e(TAG, "Failed to start download", e); } @@ -290,7 +294,7 @@ public void onClick(DialogInterface dialog, int which) { if (!selectedTrackKeys.isEmpty() || trackKeys.isEmpty()) { // We have selected keys, or we're dealing with single stream content. DownloadAction downloadAction = - downloadHelper.getDownloadAction(Util.getUtf8Bytes(name), selectedTrackKeys); + downloadHelper.getDownloadAction(Util.getUtf8Bytes(name), selectedTrackKeys); startDownload(downloadAction); } } diff --git a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/PlayerActivity.java index c9c3f749..005c4970 100644 --- a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -79,20 +79,22 @@ import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.EventLogger; import com.google.android.exoplayer2.util.Util; +import com.mux.stats.sdk.core.MuxSDKViewOrientation; +import com.mux.stats.sdk.core.model.CustomerPlayerData; +import com.mux.stats.sdk.core.model.CustomerVideoData; +import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; import java.lang.reflect.Constructor; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; import java.util.List; import java.util.UUID; -import com.mux.stats.sdk.core.MuxSDKViewOrientation; -import com.mux.stats.sdk.core.model.CustomerPlayerData; -import com.mux.stats.sdk.core.model.CustomerVideoData; -import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; -/** An activity that plays media using {@link SimpleExoPlayer}. */ +/** + * An activity that plays media using {@link SimpleExoPlayer}. + */ public class PlayerActivity extends Activity - implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener { + implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener { public static final String DRM_SCHEME_EXTRA = "drm_scheme"; public static final String DRM_LICENSE_URL_EXTRA = "drm_license_url"; @@ -104,7 +106,7 @@ public class PlayerActivity extends Activity public static final String EXTENSION_EXTRA = "extension"; public static final String ACTION_VIEW_LIST = - "com.google.android.exoplayer.demo.action.VIEW_LIST"; + "com.google.android.exoplayer.demo.action.VIEW_LIST"; public static final String URI_LIST_EXTRA = "uri_list"; public static final String EXTENSION_LIST_EXTRA = "extension_list"; @@ -130,6 +132,7 @@ public class PlayerActivity extends Activity private static final String KEY_AUTO_PLAY = "auto_play"; private static final CookieManager DEFAULT_COOKIE_MANAGER; + static { DEFAULT_COOKIE_MANAGER = new CookieManager(); DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); @@ -285,7 +288,7 @@ public void onDestroy() { @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { + @NonNull int[] grantResults) { if (grantResults.length == 0) { // Empty results are triggered if a permission is requested while another request was already // pending and can be safely ignored in this case. @@ -328,12 +331,12 @@ public void onClick(View view) { int rendererIndex = (int) view.getTag(); int rendererType = mappedTrackInfo.getRendererType(rendererIndex); boolean allowAdaptiveSelections = - rendererType == C.TRACK_TYPE_VIDEO - || (rendererType == C.TRACK_TYPE_AUDIO - && mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) - == MappedTrackInfo.RENDERER_SUPPORT_NO_TRACKS); + rendererType == C.TRACK_TYPE_VIDEO + || (rendererType == C.TRACK_TYPE_AUDIO + && mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) + == MappedTrackInfo.RENDERER_SUPPORT_NO_TRACKS); Pair dialogPair = - TrackSelectionView.getDialog(this, title, trackSelector, rendererIndex); + TrackSelectionView.getDialog(this, title, trackSelector, rendererIndex); dialogPair.second.setShowDisableOption(true); dialogPair.second.setAllowAdaptiveSelections(allowAdaptiveSelections); dialogPair.first.show(); @@ -364,8 +367,8 @@ private void initializePlayer() { Uri[] uris; String[] extensions; if (ACTION_VIEW.equals(action)) { - uris = new Uri[] {intent.getData()}; - extensions = new String[] {intent.getStringExtra(EXTENSION_EXTRA)}; + uris = new Uri[]{intent.getData()}; + extensions = new String[]{intent.getStringExtra(EXTENSION_EXTRA)}; } else if (ACTION_VIEW_LIST.equals(action)) { String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA); uris = new Uri[uriStrings.length]; @@ -394,7 +397,7 @@ private void initializePlayer() { if (intent.hasExtra(DRM_SCHEME_EXTRA) || intent.hasExtra(DRM_SCHEME_UUID_EXTRA)) { String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL_EXTRA); String[] keyRequestPropertiesArray = - intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA); + intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA); boolean multiSession = intent.getBooleanExtra(DRM_MULTI_SESSION_EXTRA, false); int errorStringId = R.string.error_drm_unknown; if (Util.SDK_INT < 18) { @@ -402,18 +405,18 @@ private void initializePlayer() { } else { try { String drmSchemeExtra = intent.hasExtra(DRM_SCHEME_EXTRA) ? DRM_SCHEME_EXTRA - : DRM_SCHEME_UUID_EXTRA; + : DRM_SCHEME_UUID_EXTRA; UUID drmSchemeUuid = Util.getDrmUuid(intent.getStringExtra(drmSchemeExtra)); if (drmSchemeUuid == null) { errorStringId = R.string.error_drm_unsupported_scheme; } else { drmSessionManager = - buildDrmSessionManagerV18( - drmSchemeUuid, drmLicenseUrl, keyRequestPropertiesArray, multiSession); + buildDrmSessionManagerV18( + drmSchemeUuid, drmLicenseUrl, keyRequestPropertiesArray, multiSession); } } catch (UnsupportedDrmException e) { errorStringId = e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME - ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown; + ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown; } } if (drmSessionManager == null) { @@ -436,28 +439,29 @@ private void initializePlayer() { } boolean preferExtensionDecoders = - intent.getBooleanExtra(PREFER_EXTENSION_DECODERS_EXTRA, false); + intent.getBooleanExtra(PREFER_EXTENSION_DECODERS_EXTRA, false); @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode = - ((DemoApplication) getApplication()).useExtensionRenderers() - ? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; + ((DemoApplication) getApplication()).useExtensionRenderers() + ? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; DefaultRenderersFactory renderersFactory = - new DefaultRenderersFactory(this, extensionRendererMode); + new DefaultRenderersFactory(this, extensionRendererMode); trackSelector = new DefaultTrackSelector(trackSelectionFactory); trackSelector.setParameters(trackSelectorParameters); lastSeenTrackGroupArray = null; player = - ExoPlayerFactory.newSimpleInstance( - /* context= */ this, renderersFactory, trackSelector, drmSessionManager); + ExoPlayerFactory.newSimpleInstance( + /* context= */ this, renderersFactory, trackSelector, drmSessionManager); player.addListener(new PlayerEventListener()); CustomerPlayerData customerPlayerData = new CustomerPlayerData(); customerPlayerData.setEnvironmentKey("YOUR_ENVIRONMENT_KEY"); CustomerVideoData customerVideoData = new CustomerVideoData(); customerVideoData.setVideoTitle(intent.getStringExtra(VIDEO_TITLE_EXTRA)); - muxStats = new MuxStatsExoPlayer(this, player, "demo-player", customerPlayerData, customerVideoData, false); + muxStats = new MuxStatsExoPlayer(this, player, "demo-player", customerPlayerData, + customerVideoData, false); Point size = new Point(); getWindowManager().getDefaultDisplay().getSize(size); muxStats.setScreenSize(size.x, size.y); @@ -476,7 +480,7 @@ private void initializePlayer() { mediaSources[i] = buildMediaSource(uris[i], extensions[i]); } mediaSource = - mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources); + mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources); String adTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA); if (adTagUriString != null) { Uri adTagUri = Uri.parse(adTagUriString); @@ -512,19 +516,19 @@ private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension switch (type) { case C.TYPE_DASH: return new DashMediaSource.Factory(dataSourceFactory) - .setManifestParser( - new FilteringManifestParser<>(new DashManifestParser(), getOfflineStreamKeys(uri))) - .createMediaSource(uri); + .setManifestParser( + new FilteringManifestParser<>(new DashManifestParser(), getOfflineStreamKeys(uri))) + .createMediaSource(uri); case C.TYPE_SS: return new SsMediaSource.Factory(dataSourceFactory) - .setManifestParser( - new FilteringManifestParser<>(new SsManifestParser(), getOfflineStreamKeys(uri))) - .createMediaSource(uri); + .setManifestParser( + new FilteringManifestParser<>(new SsManifestParser(), getOfflineStreamKeys(uri))) + .createMediaSource(uri); case C.TYPE_HLS: return new HlsMediaSource.Factory(dataSourceFactory) - .setPlaylistParserFactory( - new DefaultHlsPlaylistParserFactory(getOfflineStreamKeys(uri))) - .createMediaSource(uri); + .setPlaylistParserFactory( + new DefaultHlsPlaylistParserFactory(getOfflineStreamKeys(uri))) + .createMediaSource(uri); case C.TYPE_OTHER: return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); default: { @@ -538,16 +542,16 @@ private List getOfflineStreamKeys(Uri uri) { } private DefaultDrmSessionManager buildDrmSessionManagerV18( - UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession) - throws UnsupportedDrmException { + UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession) + throws UnsupportedDrmException { HttpDataSource.Factory licenseDataSourceFactory = - ((DemoApplication) getApplication()).buildHttpDataSourceFactory(); + ((DemoApplication) getApplication()).buildHttpDataSourceFactory(); HttpMediaDrmCallback drmCallback = - new HttpMediaDrmCallback(licenseUrl, licenseDataSourceFactory); + new HttpMediaDrmCallback(licenseUrl, licenseDataSourceFactory); if (keyRequestPropertiesArray != null) { for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) { drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i], - keyRequestPropertiesArray[i + 1]); + keyRequestPropertiesArray[i + 1]); } } releaseMediaDrm(); @@ -610,13 +614,18 @@ private void clearStartPosition() { startPosition = C.TIME_UNSET; } - /** Returns a new DataSource factory. */ + /** + * Returns a new DataSource factory. + */ private DataSource.Factory buildDataSourceFactory() { return ((DemoApplication) getApplication()).buildDataSourceFactory(); } - /** Returns an ads media source, reusing the ads loader if one exists. */ - private @Nullable MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { + /** + * Returns an ads media source, reusing the ads loader if one exists. + */ + private @Nullable + MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { // Load the extension source using reflection so the demo app doesn't have to depend on it. // The ads loader is reused for multiple playbacks, so that ad playback can resume. try { @@ -625,25 +634,25 @@ private DataSource.Factory buildDataSourceFactory() { // Full class names used so the LINT.IfChange rule triggers should any of the classes move. // LINT.IfChange Constructor loaderConstructor = - loaderClass - .asSubclass(AdsLoader.class) - .getConstructor(android.content.Context.class, android.net.Uri.class); + loaderClass + .asSubclass(AdsLoader.class) + .getConstructor(android.content.Context.class, android.net.Uri.class); // LINT.ThenChange(../../../../../../../../proguard-rules.txt) adsLoader = loaderConstructor.newInstance(this, adTagUri); } adsLoader.setPlayer(player); AdsMediaSource.MediaSourceFactory adMediaSourceFactory = - new AdsMediaSource.MediaSourceFactory() { - @Override - public MediaSource createMediaSource(Uri uri) { - return PlayerActivity.this.buildMediaSource(uri); - } - - @Override - public int[] getSupportedTypes() { - return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; - } - }; + new AdsMediaSource.MediaSourceFactory() { + @Override + public MediaSource createMediaSource(Uri uri) { + return PlayerActivity.this.buildMediaSource(uri); + } + + @Override + public int[] getSupportedTypes() { + return new int[]{C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; + } + }; return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView); } catch (ClassNotFoundException e) { // IMA extension not loaded. @@ -757,11 +766,11 @@ public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray tra MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); if (mappedTrackInfo != null) { if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) - == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { + == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { showToast(R.string.error_unsupported_video); } if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_AUDIO) - == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { + == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { showToast(R.string.error_unsupported_audio); } } @@ -780,23 +789,23 @@ public Pair getErrorMessage(ExoPlaybackException e) { if (cause instanceof DecoderInitializationException) { // Special case for decoder initialization failures. DecoderInitializationException decoderInitializationException = - (DecoderInitializationException) cause; + (DecoderInitializationException) cause; 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); + getString( + R.string.error_no_secure_decoder, decoderInitializationException.mimeType); } else { errorString = - getString(R.string.error_no_decoder, decoderInitializationException.mimeType); + getString(R.string.error_no_decoder, decoderInitializationException.mimeType); } } else { errorString = - getString( - R.string.error_instantiating_decoder, - decoderInitializationException.decoderName); + getString( + R.string.error_instantiating_decoder, + decoderInitializationException.decoderName); } } } diff --git a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index a033627d..c2c7ac30 100644 --- a/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demo/src/r2_9_6/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -49,7 +49,9 @@ import java.util.Collections; import java.util.List; -/** An activity for selecting from a list of media samples. */ +/** + * An activity for selecting from a list of media samples. + */ public class SampleChooserActivity extends Activity implements DownloadTracker.Listener, OnChildClickListener { @@ -71,7 +73,7 @@ public void onCreate(Bundle savedInstanceState) { String dataUri = intent.getDataString(); String[] uris; if (dataUri != null) { - uris = new String[] {dataUri}; + uris = new String[]{dataUri}; } else { ArrayList uriList = new ArrayList<>(); AssetManager assetManager = getAssets(); @@ -450,6 +452,7 @@ public SampleGroup(String title) { } private static final class DrmInfo { + public final String drmScheme; public final String drmLicenseUrl; public final String[] drmKeyRequestProperties; @@ -476,6 +479,7 @@ public void updateIntent(Intent intent) { } private abstract static class Sample { + public final String name; public final boolean preferExtensionDecoders; public final String abrAlgorithm; @@ -527,7 +531,7 @@ public Intent buildIntent(Context context) { .setData(uri) .putExtra(PlayerActivity.EXTENSION_EXTRA, extension) .putExtra(PlayerActivity.AD_TAG_URI_EXTRA, adTagUri) - .putExtra(PlayerActivity.VIDEO_TITLE_EXTRA, name) + .putExtra(PlayerActivity.VIDEO_TITLE_EXTRA, name) .setAction(PlayerActivity.ACTION_VIEW); } diff --git a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java index ac8be7dc..cdbb2110 100644 --- a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -54,19 +54,25 @@ public void onCreate() { userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); } - /** Returns a {@link DataSource.Factory}. */ + /** + * Returns a {@link DataSource.Factory}. + */ public DataSource.Factory buildDataSourceFactory() { DefaultDataSourceFactory upstreamFactory = new DefaultDataSourceFactory(this, buildHttpDataSourceFactory()); return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache()); } - /** Returns a {@link HttpDataSource.Factory}. */ + /** + * Returns a {@link HttpDataSource.Factory}. + */ public HttpDataSource.Factory buildHttpDataSourceFactory() { return new DefaultHttpDataSourceFactory(userAgent); } - /** Returns whether extension renderers should be used. */ + /** + * Returns whether extension renderers should be used. + */ public boolean useExtensionRenderers() { return "withExtensions".equals(BuildConfig.FLAVOR); } diff --git a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index 7d1ab16c..b5936cc3 100644 --- a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -24,7 +24,9 @@ import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.Util; -/** A service for downloading media. */ +/** + * A service for downloading media. + */ public class DemoDownloadService extends DownloadService { private static final String CHANNEL_ID = "download_channel"; diff --git a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java index b0619a82..3c243f7b 100644 --- a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -64,10 +64,14 @@ */ public class DownloadTracker implements DownloadManager.Listener { - /** Listens for changes in the tracked downloads. */ + /** + * Listens for changes in the tracked downloads. + */ public interface Listener { - /** Called when the tracked downloads changed. */ + /** + * Called when the tracked downloads changed. + */ void onDownloadsChanged(); } @@ -274,7 +278,7 @@ public void onPrepared(DownloadHelper helper) { @Override public void onPrepareError(DownloadHelper helper, IOException e) { Toast.makeText( - context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG) + context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG) .show(); Log.e(TAG, "Failed to start download", e); } diff --git a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java index b286fa92..9a5ae5c4 100644 --- a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -84,7 +84,6 @@ import com.mux.stats.sdk.core.model.CustomerPlayerData; import com.mux.stats.sdk.core.model.CustomerVideoData; import com.mux.stats.sdk.muxstats.MuxStatsExoPlayer; - import java.lang.reflect.Constructor; import java.net.CookieHandler; import java.net.CookieManager; @@ -92,7 +91,9 @@ import java.util.List; import java.util.UUID; -/** An activity that plays media using {@link SimpleExoPlayer}. */ +/** + * An activity that plays media using {@link SimpleExoPlayer}. + */ public class PlayerActivity extends Activity implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener { @@ -132,6 +133,7 @@ public class PlayerActivity extends Activity private static final String KEY_AUTO_PLAY = "auto_play"; private static final CookieManager DEFAULT_COOKIE_MANAGER; + static { DEFAULT_COOKIE_MANAGER = new CookieManager(); DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); @@ -332,8 +334,8 @@ public void onClick(View view) { boolean allowAdaptiveSelections = rendererType == C.TRACK_TYPE_VIDEO || (rendererType == C.TRACK_TYPE_AUDIO - && mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) - == MappedTrackInfo.RENDERER_SUPPORT_NO_TRACKS); + && mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) + == MappedTrackInfo.RENDERER_SUPPORT_NO_TRACKS); Pair dialogPair = TrackSelectionView.getDialog(this, title, trackSelector, rendererIndex); dialogPair.second.setShowDisableOption(true); @@ -366,8 +368,8 @@ private void initializePlayer() { Uri[] uris; String[] extensions; if (ACTION_VIEW.equals(action)) { - uris = new Uri[] {intent.getData()}; - extensions = new String[] {intent.getStringExtra(EXTENSION_EXTRA)}; + uris = new Uri[]{intent.getData()}; + extensions = new String[]{intent.getStringExtra(EXTENSION_EXTRA)}; } else if (ACTION_VIEW_LIST.equals(action)) { String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA); uris = new Uri[uriStrings.length]; @@ -452,14 +454,16 @@ private void initializePlayer() { lastSeenTrackGroupArray = null; player = - ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector, drmSessionManager); + ExoPlayerFactory + .newSimpleInstance(this, renderersFactory, trackSelector, drmSessionManager); player.setPlayWhenReady(startAutoPlay); player.addListener(new PlayerEventListener()); CustomerPlayerData customerPlayerData = new CustomerPlayerData(); customerPlayerData.setEnvironmentKey("YOUR_ENVIRONMENT_KEY"); CustomerVideoData customerVideoData = new CustomerVideoData(); customerVideoData.setVideoTitle(intent.getStringExtra(VIDEO_TITLE_EXTRA)); - muxStats = new MuxStatsExoPlayer(this, player, "demo-player", customerPlayerData, customerVideoData); + muxStats = new MuxStatsExoPlayer(this, player, "demo-player", customerPlayerData, + customerVideoData); Point size = new Point(); getWindowManager().getDefaultDisplay().getSize(size); muxStats.setScreenSize(size.x, size.y); @@ -611,13 +615,18 @@ private void clearStartPosition() { startPosition = C.TIME_UNSET; } - /** Returns a new DataSource factory. */ + /** + * Returns a new DataSource factory. + */ private DataSource.Factory buildDataSourceFactory() { return ((DemoApplication) getApplication()).buildDataSourceFactory(); } - /** Returns an ads media source, reusing the ads loader if one exists. */ - private @Nullable MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { + /** + * Returns an ads media source, reusing the ads loader if one exists. + */ + private @Nullable + MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { // Load the extension source using reflection so the demo app doesn't have to depend on it. // The ads loader is reused for multiple playbacks, so that ad playback can resume. try { @@ -642,7 +651,7 @@ public MediaSource createMediaSource(Uri uri) { @Override public int[] getSupportedTypes() { - return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; + return new int[]{C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; } }; diff --git a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index ac7ec54b..0ad3e16b 100644 --- a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -36,8 +36,8 @@ import android.widget.TextView; import android.widget.Toast; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.demo.DownloadTracker; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.demo.DownloadTracker; import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSourceInputStream; @@ -54,7 +54,9 @@ import java.util.Collections; import java.util.List; -/** An activity for selecting from a list of media samples. */ +/** + * An activity for selecting from a list of media samples. + */ public class SampleChooserActivity extends Activity implements DownloadTracker.Listener, OnChildClickListener { @@ -79,7 +81,7 @@ public void onCreate(Bundle savedInstanceState) { String dataUri = intent.getDataString(); String[] uris; if (dataUri != null) { - uris = new String[] {dataUri}; + uris = new String[]{dataUri}; } else { ArrayList uriList = new ArrayList<>(); AssetManager assetManager = getAssets(); @@ -492,6 +494,7 @@ public SampleGroup(String title) { } private static final class DrmInfo { + public final String drmScheme; public final String drmLicenseUrl; public final String[] drmKeyRequestProperties; @@ -518,6 +521,7 @@ public void updateIntent(Intent intent) { } private abstract static class Sample { + public final String name; public final DrmInfo drmInfo; diff --git a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index d9676d8c..8f4afaf8 100644 --- a/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/demo/src/r2_9_6_ads/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -80,31 +80,36 @@ * #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling * {@link #release()}. * - *

The IMA SDK can take into account video control overlay views when calculating ad viewability. - * For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} and {@link - * AdViewProvider#getAdOverlayViews()}. + *

The IMA SDK can take into account video control overlay views when calculating ad + * viewability. For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} + * and {@link AdViewProvider#getAdOverlayViews()}. */ public final class ImaAdsLoader implements Player.EventListener, - AdsLoader, - VideoAdPlayer, - ContentProgressProvider, - AdErrorListener, - AdsLoadedListener, - AdEventListener { + AdsLoader, + VideoAdPlayer, + ContentProgressProvider, + AdErrorListener, + AdsLoadedListener, + AdEventListener { static { ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); } - /** Builder for {@link ImaAdsLoader}. */ + /** + * Builder for {@link ImaAdsLoader}. + */ public static final class Builder { private final Context context; - @Nullable private ImaSdkSettings imaSdkSettings; - @Nullable private AdEventListener adEventListener; - @Nullable private Set adUiElements; + @Nullable + private ImaSdkSettings imaSdkSettings; + @Nullable + private AdEventListener adEventListener; + @Nullable + private Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; private int mediaBitrate; @@ -140,8 +145,7 @@ public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { } /** - * Sets a listener for ad events that will be passed to {@link - * AdsManager#addAdEventListener(AdEventListener)}. + * Sets a listener for ad events that will be passed to {@link AdsManager#addAdEventListener(AdEventListener)}. * * @param adEventListener The ad event listener. * @return This builder, for convenience. @@ -207,7 +211,7 @@ public Builder setMaxMediaBitrate(int bitrate) { * setting is {@code true}. * * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on - * Android TV devices. + * Android TV devices. * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ @@ -225,9 +229,8 @@ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvaila /** * Returns a new {@link ImaAdsLoader} for the specified ad tag. * - * @param adTagUri The URI of a compatible ad tag to load. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * information on compatible ad tags. + * @param adTagUri The URI of a compatible ad tag to load. See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for information on compatible ad tags. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdTag(Uri adTagUri) { @@ -249,7 +252,7 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) { * Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response. * * @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of - * making a request via an ad tag URL. + * making a request via an ad tag URL. * @return The new {@link ImaAdsLoader}. */ public ImaAdsLoader buildForAdsResponse(String adsResponse) { @@ -279,7 +282,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { private static final String IMA_SDK_SETTINGS_PLAYER_TYPE = "google/exo.ext.ima"; private static final String IMA_SDK_SETTINGS_PLAYER_VERSION = ExoPlayerLibraryInfo.VERSION; - /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ + /** + * The value used in {@link VideoProgressUpdate}s to indicate an unset duration. + */ private static final long IMA_DURATION_UNSET = -1L; /** @@ -288,17 +293,24 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final long END_OF_CONTENT_POSITION_THRESHOLD_MS = 5000; - /** The maximum duration before an ad break that IMA may start preloading the next ad. */ + /** + * The maximum duration before an ad break that IMA may start preloading the next ad. + */ private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000; private static final int TIMEOUT_UNSET = -1; private static final int BITRATE_UNSET = -1; - /** The state of ad playback. */ + /** + * The state of ad playback. + */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) - private @interface ImaAdState {} + private @interface ImaAdState { + + } + /** * The ad playback state when IMA is not playing an ad. */ @@ -312,25 +324,32 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { */ private static final int IMA_AD_STATE_PAUSED = 2; - private final @Nullable Uri adTagUri; - private final @Nullable String adsResponse; + private final @Nullable + Uri adTagUri; + private final @Nullable + String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; private final boolean focusSkipButtonWhenAvailable; private final int mediaBitrate; - private final @Nullable Set adUiElements; - private final @Nullable AdEventListener adEventListener; + private final @Nullable + Set adUiElements; + private final @Nullable + AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; private final List adCallbacks; private final AdDisplayContainer adDisplayContainer; private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader; - @Nullable private Player nextPlayer; + @Nullable + private Player nextPlayer; private Object pendingAdRequestContext; private List supportedMimeTypes; - @Nullable private EventListener eventListener; - @Nullable private Player player; + @Nullable + private EventListener eventListener; + @Nullable + private Player player; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercentage; @@ -344,14 +363,23 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking IMA's state. - /** The expected ad group index that IMA should load next. */ + /** + * The expected ad group index that IMA should load next. + */ private int expectedAdGroupIndex; - /** The index of the current ad group that IMA is loading. */ + /** + * The index of the current ad group that IMA is loading. + */ private int adGroupIndex; - /** Whether IMA has sent an ad event to pause content since the last resume content event. */ + /** + * Whether IMA has sent an ad event to pause content since the last resume content event. + */ private boolean imaPausedContent; - /** The current ad playback state. */ - private @ImaAdState int imaAdState; + /** + * The current ad playback state. + */ + private @ImaAdState + int imaAdState; /** * Whether {@link com.google.ads.interactivemedia.v3.api.AdsLoader#contentComplete()} has been * called since starting ad playback. @@ -360,7 +388,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { // Fields tracking the player/loader state. - /** Whether the player is playing an ad. */ + /** + * Whether the player is playing an ad. + */ private boolean playingAd; /** * If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET} @@ -383,9 +413,13 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * content progress should increase. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressOffsetMs; - /** Stores the pending content position when a seek operation was intercepted to play an ad. */ + /** + * Stores the pending content position when a seek operation was intercepted to play an ad. + */ private long pendingContentPositionMs; - /** Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. */ + /** + * Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. + */ private boolean sentPendingContentPositionMs; /** @@ -393,10 +427,10 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { * *

If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead. * - * @param context The context. + * @param context The context. * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. */ public ImaAdsLoader(Context context, Uri adTagUri) { this( @@ -416,12 +450,13 @@ public ImaAdsLoader(Context context, Uri adTagUri) { /** * Creates a new IMA ads loader. * - * @param context The context. - * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See - * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for - * more information. + * @param context The context. + * @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility + * for more information. * @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to - * use the default settings. If set, the player type and version fields may be overwritten. + * use the default settings. If set, the player type and version fields may + * be overwritten. * @deprecated Use {@link ImaAdsLoader.Builder}. */ @Deprecated @@ -485,8 +520,8 @@ private ImaAdsLoader( } /** - * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by - * this instance. + * Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by this + * instance. */ public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() { return adsLoader; @@ -1350,7 +1385,7 @@ private void maybeNotifyInternalError(String name, Exception cause) { private static long[] getAdGroupTimesUs(List cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. - return new long[] {0}; + return new long[]{0}; } int count = cuePoints.size(); @@ -1388,24 +1423,44 @@ private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) { } } - /** Factory for objects provided by the IMA SDK. */ + /** + * Factory for objects provided by the IMA SDK. + */ // @VisibleForTesting /* package */ interface ImaFactory { - /** @see ImaSdkSettings */ + + /** + * @see ImaSdkSettings + */ ImaSdkSettings createImaSdkSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() + */ AdsRenderingSettings createAdsRenderingSettings(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() + */ AdDisplayContainer createAdDisplayContainer(); - /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */ + + /** + * @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() + */ AdsRequest createAdsRequest(); - /** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */ + + /** + * @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) + */ com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer); } - /** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */ + /** + * Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. + */ private static final class DefaultImaFactory implements ImaFactory { + @Override public ImaSdkSettings createImaSdkSettings() { return ImaSdkFactory.getInstance().createImaSdkSettings();