package net.alexandroid.utils.exoplayerhelper; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import com.google.ads.interactivemedia.v3.api.AdEvent; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.SingleSampleMediaSource; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.FileDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSinkFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.File; import java.io.IOException; import java.util.ArrayList; @SuppressWarnings("WeakerAccess") public class ExoPlayerHelper implements View.OnClickListener, View.OnTouchListener, ExoPlayerControl, ExoPlayerStatus, Player.EventListener, ImaAdsLoader.VideoAdPlayerCallback, AdsMediaSource.MediaSourceFactory, AdEvent.AdEventListener { public static final String PARAM_AUTO_PLAY = "PARAM_AUTO_PLAY"; public static final String PARAM_WINDOW = "PARAM_WINDOW"; public static final String PARAM_POSITION = "PARAM_POSITION"; public static final String PARAM_IS_AD_WAS_SHOWN = "PARAM_IS_AD_WAS_SHOWN"; private Context mContext; private PlayerView mExoPlayerView; private SimpleExoPlayer mPlayer; private ImaAdsLoader mImaAdsLoader; private DataSource.Factory mDataSourceFactory; private DefaultLoadControl mLoadControl; private MediaSource mMediaSource; private ExoAdListener mExoAdListener; private ExoPlayerListener mExoPlayerListener; private ExoThumbListener mExoThumbListener; private ProgressBar mProgressBar; private ImageView mBtnMute; private ImageView mBtnFullScreen; private ImageView mThumbImage; private Uri[] mVideosUris; private ArrayList<String> mSubTitlesUrls; private String mTagUrl; private long mResumePosition = C.TIME_UNSET; private int mResumeWindow = C.INDEX_UNSET; private float mTempCurrentVolume; private boolean isVideoMuted; private boolean isAdMuted; private boolean isRepeatModeOn; private boolean isAutoPlayOn; private boolean isResumePlayWhenReady; private boolean isAdWasShown; private boolean isPlayerPrepared; private boolean isToPrepareOnResume = true; private boolean isThumbImageViewEnabled; private boolean isLiveStreamSupportEnabled; private LinearLayout mBottomProgress; private ExoPlayerHelper(Context context, PlayerView exoPlayerView) { if (context == null) { throw new IllegalArgumentException("ExoPlayerHelper constructor - Context can't be null"); } if (!(context instanceof Activity)) { throw new IllegalArgumentException("ExoPlayerHelper constructor - Context must be an instance of Activity"); } if (exoPlayerView == null) { throw new IllegalArgumentException("ExoPlayerHelper constructor - SimpleExoPlayerView can't be null"); } mContext = context; mExoPlayerView = exoPlayerView; mBottomProgress = mExoPlayerView.findViewById(R.id.bottom_progress); setVideoClickable(); setControllerListener(); init(); } private void addProgressBar(int color) { FrameLayout frameLayout = mExoPlayerView.getOverlayFrameLayout(); if (frameLayout == null) { return; } mProgressBar = frameLayout.findViewById(R.id.progressBar); if (mProgressBar != null) { return; } mProgressBar = new ProgressBar(mContext, null, android.R.attr.progressBarStyleLarge); mProgressBar.setId(R.id.progressBar); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER; mProgressBar.setLayoutParams(params); mProgressBar.setIndeterminate(true); mProgressBar.getIndeterminateDrawable().setColorFilter( color == 0 ? Color.RED : color, android.graphics.PorterDuff.Mode.SRC_IN); mProgressBar.setVisibility(View.GONE); frameLayout.addView(mProgressBar); } @SuppressLint("ClickableViewAccessibility") private void setVideoClickable() { mExoPlayerView.setOnTouchListener(this); } @SuppressLint("ClickableViewAccessibility") private void setControllerListener() { mExoPlayerView.findViewById(R.id.exo_play).setOnTouchListener(this); mExoPlayerView.findViewById(R.id.exo_pause).setOnTouchListener(this); mBtnMute = mExoPlayerView.findViewById(R.id.btnMute); mBtnFullScreen = mExoPlayerView.findViewById(R.id.btnFullScreen); mBtnMute.setOnTouchListener(this); mBtnFullScreen.setOnTouchListener(this); } private void init() { // Measures bandwidth during playback. Can be null if not required. DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(mContext).build(); // Produces DataSource instances through which media data is loaded. mDataSourceFactory = new DefaultDataSourceFactory(mContext, Util.getUserAgent(mContext, mContext.getString(R.string.app_name)), bandwidthMeter); // LoadControl that controls when the MediaSource buffers more media, and how much media is buffered. // LoadControl is injected when the player is created. //removed deprecated DefaultLoadControl creation method DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder(); builder.setAllocator(new DefaultAllocator(true, 2 * 1024 * 1024)); builder.setBufferDurationsMs(5000, 5000, 5000, 5000); builder.setPrioritizeTimeOverSizeThresholds(true); mLoadControl = builder.createDefaultLoadControl(); } // Player creation and release private void setVideoUrls(String[] urls) { mVideosUris = new Uri[urls.length]; for (int i = 0; i < urls.length; i++) { mVideosUris[i] = Uri.parse(urls[i]); } } private void setSubtitlesUrls(ArrayList<String> list) { mSubTitlesUrls = list; } private void createMediaSource() { // A MediaSource defines the media to be played, loads the media, and from which the loaded media can be read. // A MediaSource is injected via ExoPlayer.prepare at the start of playback. if (mVideosUris == null) { return; } MediaSource[] mediaSources = new MediaSource[mVideosUris.length]; for (int i = 0; i < mVideosUris.length; i++) { mediaSources[i] = buildMediaSource(mVideosUris[i]); if (mSubTitlesUrls != null && i < mSubTitlesUrls.size() & mSubTitlesUrls.get(i) != null) { mediaSources[i] = addSubTitlesToMediaSource(mediaSources[i], mSubTitlesUrls.get(i)); } } mMediaSource = mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources); addAdsToMediaSource(); } private MediaSource addSubTitlesToMediaSource(MediaSource mediaSource, String subTitlesUrl) { Format textFormat = Format.createTextSampleFormat(null, MimeTypes.APPLICATION_SUBRIP, null, Format.NO_VALUE, Format.NO_VALUE, "en", Format.NO_VALUE, null); Uri uri = Uri.parse(subTitlesUrl); MediaSource subtitleSource = new SingleSampleMediaSource.Factory(mDataSourceFactory) .createMediaSource(uri, textFormat, C.TIME_UNSET); return new MergingMediaSource(mediaSource, subtitleSource); } private MediaSource buildMediaSource(Uri uri) { int type = Util.inferContentType(uri); switch (type) { case C.TYPE_SS: return new SsMediaSource.Factory(mDataSourceFactory).createMediaSource(uri); case C.TYPE_DASH: return new DashMediaSource.Factory(mDataSourceFactory).createMediaSource(uri); case C.TYPE_HLS: return new HlsMediaSource.Factory(mDataSourceFactory).createMediaSource(uri); case C.TYPE_OTHER: return new ProgressiveMediaSource.Factory(mDataSourceFactory).createMediaSource(uri); default: { throw new IllegalStateException("Unsupported type: " + type); } } } private void addAdsToMediaSource() { if (mMediaSource == null) { throw new IllegalStateException("setVideoUrls must be invoked before setTagUrl (mMediaSource is null)"); } if (mTagUrl == null || isAdWasShown) { return; } if (mImaAdsLoader == null) { mImaAdsLoader = new ImaAdsLoader.Builder(mContext) .setAdEventListener(this) .buildForAdTag(Uri.parse(mTagUrl)); //mImaAdsLoader = new ImaAdsLoader(mContext, Uri.parse(mTagUrl)); mImaAdsLoader.addCallback(this); mImaAdsLoader.setPlayer(mPlayer); } //Set player using adsLoader.setPlayer before preparing the player. mMediaSource = new AdsMediaSource(mMediaSource, mDataSourceFactory, mImaAdsLoader, mExoPlayerView); //mMediaSource.addEventListener(null, null); /* mMediaSource = new AdsMediaSource( mMediaSource, this, mImaAdsLoader, mExoPlayerView.getOverlayFrameLayout());*/ } private void setProgressVisible(boolean visible) { if (mProgressBar != null) { mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE); } } private void addThumbImageView() { if (mThumbImage != null) { return; } AspectRatioFrameLayout frameLayout = mExoPlayerView.findViewById(R.id.exo_content_frame); mThumbImage = new ImageView(mContext); mThumbImage.setId(R.id.thumbImg); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); params.gravity = Gravity.CENTER; mThumbImage.setLayoutParams(params); mThumbImage.setBackgroundColor(Color.BLACK); frameLayout.addView(mThumbImage); if (mExoThumbListener != null) { mExoThumbListener.onThumbImageViewReady(mThumbImage); } } private void removeThumbImageView() { if (mThumbImage != null) { AspectRatioFrameLayout frameLayout = mExoPlayerView.findViewById(R.id.exo_content_frame); frameLayout.removeView(mThumbImage); mThumbImage = null; } } private void setUiControllersVisibility(boolean visibility) { mExoPlayerView.setUseController(visibility); if (!visibility) { AspectRatioFrameLayout frameLayout = mExoPlayerView.findViewById(R.id.exo_content_frame); frameLayout.setOnClickListener(this); } } @SuppressLint("RtlHardcoded") private void addMuteButton(boolean isAdMuted, boolean isVideoMuted) { this.isVideoMuted = isVideoMuted; this.isAdMuted = isAdMuted; mBtnMute.setImageResource(this.isVideoMuted ? R.drawable.ic_action_mute : R.drawable.ic_action_volume_up); /* FrameLayout frameLayout = mExoPlayerView.getOverlayFrameLayout(); mBtnMute = new ImageView(mContext); mBtnMute.setId(R.id.muteBtn); mBtnMute.setImageResource(this.isVideoMuted ? R.drawable.ic_action_mute : R.drawable.ic_action_volume_up); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); params.gravity = Gravity.LEFT | Gravity.BOTTOM; //params.bottomMargin = Math.round(3 * mExoPlayerView.getContext().getResources().getDisplayMetrics().density); mBtnMute.setLayoutParams(params); mBtnMute.setOnClickListener(this); frameLayout.addView(mBtnMute);*/ } private void updateMutedStatus() { boolean isMuted = (mPlayer.isPlayingAd() && isAdMuted) || (!mPlayer.isPlayingAd() && isVideoMuted); if (isMuted) { mPlayer.setVolume(0f); } else { mPlayer.setVolume(mTempCurrentVolume); } if (mBtnMute != null) { mBtnMute.setImageResource(isMuted ? R.drawable.ic_action_mute : R.drawable.ic_action_volume_up); } } private void enableCache(int maxCacheSizeMb) { LeastRecentlyUsedCacheEvictor evictor = new LeastRecentlyUsedCacheEvictor(maxCacheSizeMb * 1024 * 1024); File file = new File(mContext.getCacheDir(), "media"); Log.d("ZAQ", "enableCache (" + maxCacheSizeMb + " MB), file: " + file.getAbsolutePath()); SimpleCache simpleCache = new SimpleCache(file, evictor); mDataSourceFactory = new CacheDataSourceFactory( simpleCache, mDataSourceFactory, new FileDataSourceFactory(), new CacheDataSinkFactory(simpleCache, 2 * 1024 * 1024), CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, new CacheDataSource.EventListener() { @Override public void onCacheIgnored(int reason) { Log.d("ZAQ", "onCacheIgnored"); } @Override public void onCachedBytesRead(long cacheSizeBytes, long cachedBytesRead) { Log.d("ZAQ", "onCachedBytesRead , cacheSizeBytes: " + cacheSizeBytes + " cachedBytesRead: " + cachedBytesRead); } }); } @Override public void onClick(View v) { // On video tap if (mExoPlayerListener != null && v.getId() == R.id.exo_content_frame) { mExoPlayerListener.onVideoTapped(); } } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_UP && mExoPlayerListener != null) { if (view.getId() == mExoPlayerView.getId()) { mExoPlayerListener.onVideoTapped(); } if (view.getId() == R.id.exo_play) { if (mExoPlayerListener.onPlayBtnTap()) { return true; } } if (view.getId() == R.id.exo_pause) { if (mExoPlayerListener.onPauseBtnTap()) { return true; } } if (view.getId() == R.id.btnFullScreen) { if (mExoPlayerListener != null) { mExoPlayerListener.onFullScreenBtnTap(); } return true; } if (view.getId() == R.id.btnMute) { if (mPlayer.isPlayingAd()) { isVideoMuted = isAdMuted = !isAdMuted; } else { isAdMuted = isVideoMuted = !isVideoMuted; } ((ImageView) view).setImageResource(isVideoMuted ? R.drawable.ic_action_mute : R.drawable.ic_action_volume_up); updateMutedStatus(); if (mExoPlayerListener != null) { mExoPlayerListener.onMuteStateChanged(isVideoMuted); } return true; } } // Player block FrameLayout layout = mExoPlayerView.getOverlayFrameLayout(); return layout != null && view.getId() == layout.getId(); } // Resume position saving private void addSavedInstanceState(Bundle savedInstanceState) { if (savedInstanceState != null) { isAdWasShown = savedInstanceState.getBoolean(PARAM_IS_AD_WAS_SHOWN, false); isResumePlayWhenReady = savedInstanceState.getBoolean(PARAM_AUTO_PLAY, true); mResumeWindow = savedInstanceState.getInt(PARAM_WINDOW, C.INDEX_UNSET); mResumePosition = savedInstanceState.getLong(PARAM_POSITION, C.TIME_UNSET); } } private void updateResumePosition() { isResumePlayWhenReady = mPlayer.getPlayWhenReady(); mResumeWindow = mPlayer.getCurrentWindowIndex(); mResumePosition = Math.max(0, mPlayer.getContentPosition()); } @SuppressWarnings("unused") private void clearResumePosition() { mResumeWindow = C.INDEX_UNSET; mResumePosition = C.TIME_UNSET; } private int getNextWindowIndex() { return mPlayer.getCurrentTimeline().getNextWindowIndex(mPlayer.getCurrentWindowIndex(), mPlayer.getRepeatMode(), false); } private int getPreviousWindowIndex() { return mPlayer.getCurrentTimeline().getPreviousWindowIndex(mPlayer.getCurrentWindowIndex(), mPlayer.getRepeatMode(), false); } // Player events, internal handle private void onPlayerBuffering() { if (mPlayer.getPlayWhenReady()) { setProgressVisible(true); } } private void onPlayerPlaying() { setProgressVisible(false); removeThumbImageView(); updateMutedStatus(); } private void onPlayerLoadingChanged() { liveStreamCheck(); } private void liveStreamCheck() { if (isLiveStreamSupportEnabled) { boolean isLiveStream = mPlayer.isCurrentWindowDynamic() || !mPlayer.isCurrentWindowSeekable(); /* Log.e("ZAQ", "isCurrentWindowDynamic: " + mPlayer.isCurrentWindowDynamic()); Log.e("ZAQ", "isCurrentWindowSeekable: " + mPlayer.isCurrentWindowDynamic()); Log.e("ZAQ", "isLiveStream: " + isLiveStream);*/ mBottomProgress.setVisibility(isLiveStream ? View.GONE : View.VISIBLE); } } private void onPlayerPaused() { setProgressVisible(false); } private void onAdEnded() { updateMutedStatus(); isAdWasShown = true; } @SuppressWarnings("SameParameterValue") public static class Builder { private ExoPlayerHelper mExoPlayerHelper; public Builder(Context context, PlayerView exoPlayerView) { mExoPlayerHelper = new ExoPlayerHelper(context, exoPlayerView); } public Builder addMuteButton(boolean isAdMuted, boolean isVideoMuted) { mExoPlayerHelper.addMuteButton(isAdMuted, isVideoMuted); return this; } public Builder setUiControllersVisibility(boolean visibility) { mExoPlayerHelper.setUiControllersVisibility(visibility); return this; } public Builder setVideoUrls(String... urls) { mExoPlayerHelper.setVideoUrls(urls); return this; } public Builder setSubTitlesUrls(ArrayList<String> list) { mExoPlayerHelper.setSubtitlesUrls(list); return this; } public Builder setTagUrl(String tagUrl) { mExoPlayerHelper.mTagUrl = tagUrl; return this; } public Builder setRepeatModeOn(boolean isOn) { mExoPlayerHelper.isRepeatModeOn = isOn; return this; } public Builder setAutoPlayOn(boolean isAutoPlayOn) { mExoPlayerHelper.isAutoPlayOn = isAutoPlayOn; return this; } public Builder setExoPlayerEventsListener(ExoPlayerListener exoPlayerListener) { mExoPlayerHelper.setExoPlayerEventsListener(exoPlayerListener); return this; } public Builder setExoAdEventsListener(ExoAdListener exoAdListener) { mExoPlayerHelper.setExoAdListener(exoAdListener); return this; } public Builder addSavedInstanceState(Bundle savedInstanceState) { mExoPlayerHelper.addSavedInstanceState(savedInstanceState); return this; } public Builder setThumbImageViewEnabled(ExoThumbListener exoThumbListener) { mExoPlayerHelper.setExoThumbListener(exoThumbListener); return this; } @SuppressWarnings("unused") public Builder enableCache(int maxCacheSizeMb) { mExoPlayerHelper.enableCache(maxCacheSizeMb); return this; } /** * If you have a list of videos set isToPrepareOnResume to be false * to prevent auto prepare on activity onResume/onCreate */ public Builder setToPrepareOnResume(boolean toPrepareOnResume) { mExoPlayerHelper.isToPrepareOnResume = toPrepareOnResume; return this; } public Builder enableLiveStreamSupport() { mExoPlayerHelper.isLiveStreamSupportEnabled = true; return this; } public Builder addProgressBarWithColor(int colorAccent) { mExoPlayerHelper.addProgressBar(colorAccent); return this; } public Builder setFullScreenBtnVisible() { mExoPlayerHelper.setFullScreenBtnVisibility(true); return this; } public Builder setMuteBtnVisible() { mExoPlayerHelper.mBtnMute.setVisibility(View.VISIBLE); return this; } /** * Probably you will feel a need to use that method when you need to show pre-roll ad * and you not interested in auto play. That method allows to separate player creation * from calling prepare() * Note: To play ad/content you ned to call preparePlayer() * * @return ExoPlayerHelper instance */ public ExoPlayerHelper create() { mExoPlayerHelper.createPlayer(false); return mExoPlayerHelper; } /** * Note: If you added tagUrl ad would start playing automatic even if you had set setAutoPlayOn(false) * * @return ExoPlayerHelper instance */ public ExoPlayerHelper createAndPrepare() { mExoPlayerHelper.createPlayer(true); return mExoPlayerHelper; } } /** * ExoPlayerControl interface methods */ @Override public void setExoThumbListener(ExoThumbListener exoThumbListener) { isThumbImageViewEnabled = true; mExoThumbListener = exoThumbListener; } @Override public void setExoPlayerEventsListener(ExoPlayerListener pExoPlayerListenerListener) { mExoPlayerListener = pExoPlayerListenerListener; } @Override public void setExoAdListener(ExoAdListener exoAdListener) { mExoAdListener = exoAdListener; } @Override public void createPlayer(boolean isToPrepare) { if (mExoPlayerListener != null) { mExoPlayerListener.createExoPlayerCalled(isToPrepare); } if (mPlayer != null) { return; } if (isThumbImageViewEnabled) { addThumbImageView(); } mPlayer = ExoPlayerFactory.newSimpleInstance( mContext, new DefaultRenderersFactory(mContext), new DefaultTrackSelector(), mLoadControl); mExoPlayerView.setPlayer(mPlayer); mExoPlayerView.setControllerShowTimeoutMs(1500); mExoPlayerView.setControllerHideOnTouch(false); mTempCurrentVolume = mPlayer.getVolume(); mPlayer.setRepeatMode(isRepeatModeOn ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF); mPlayer.setPlayWhenReady(isAutoPlayOn); mPlayer.addListener(this); createMediaSource(); if (isToPrepare) { preparePlayer(); } } @Override public void preparePlayer() { if (mPlayer == null || isPlayerPrepared) { return; } isPlayerPrepared = true; mPlayer.prepare(mMediaSource); if (mResumeWindow != C.INDEX_UNSET && !mPlayer.isPlayingAd()) { mPlayer.setPlayWhenReady(isResumePlayWhenReady); mPlayer.seekTo(mResumeWindow, mResumePosition + 100); if (mExoPlayerListener != null) { mExoPlayerListener.onVideoResumeDataLoaded(mResumeWindow, mResumePosition, isResumePlayWhenReady); } //mExoPlayerView.postDelayed(checkFreeze, 1000); } } // It looks like the issue was solved and no need for this Runnable private Runnable checkFreeze = new Runnable() { @Override public void run() { if (mPlayer != null && mPlayer.getPlaybackState() == Player.STATE_BUFFERING && mPlayer.getPlayWhenReady()) { Log.e("zaq", "Player.STATE_BUFFERING stuck issue"); mPlayer.seekTo(mPlayer.getContentPosition() > 500 ? mPlayer.getContentPosition() - 500 : 0); } } }; @Override public void releasePlayer() { isPlayerPrepared = false; if (mExoPlayerListener != null) { mExoPlayerListener.releaseExoPlayerCalled(); } if (mPlayer != null) { updateResumePosition(); removeThumbImageView(); mPlayer.release(); mPlayer = null; } } @Override public void seekToDefaultPosition() { if (mPlayer != null) { mPlayer.seekToDefaultPosition(); } } @Override public void releaseAdsLoader() { if (mImaAdsLoader != null) { mImaAdsLoader.release(); mImaAdsLoader = null; FrameLayout layout = mExoPlayerView.getOverlayFrameLayout(); if (layout != null) { layout.removeAllViews(); } } } @Override public void updateVideoUrls(String... urls) { if (!isPlayerPrepared) { setVideoUrls(urls); createMediaSource(); } else { throw new IllegalStateException("Can't update url's when player is prepared"); } } @Override public void playerPause() { if (mPlayer != null) { mPlayer.setPlayWhenReady(false); } } @Override public void playerPlay() { if (mPlayer != null) { mPlayer.setPlayWhenReady(true); } } @Override public void playerNext() { if (mPlayer != null) { seekTo(getNextWindowIndex(), 0); } } @Override public void playerPrevious() { if (mPlayer != null) { seekTo(getPreviousWindowIndex(), 0); } } @Override public void seekTo(int windowIndex, long positionMs) { if (mPlayer != null) { mPlayer.seekTo(windowIndex, positionMs); } } @SuppressLint("ClickableViewAccessibility") @Override public void playerBlock() { if (mExoPlayerView != null && mExoPlayerView.getOverlayFrameLayout() != null) { mExoPlayerView.getOverlayFrameLayout().setOnTouchListener(this); } } @SuppressLint("ClickableViewAccessibility") @Override public void playerUnBlock() { if (mExoPlayerView != null && mExoPlayerView.getOverlayFrameLayout() != null) { mExoPlayerView.getOverlayFrameLayout().setOnTouchListener(null); } } @Override public void setFullScreenBtnVisibility(boolean isVisible) { mBtnFullScreen.setVisibility(isVisible? View.VISIBLE : View.GONE); } @Override public void onSaveInstanceState(Bundle outState) { outState.putBoolean(PARAM_IS_AD_WAS_SHOWN, !mPlayer.isPlayingAd()); outState.putBoolean(PARAM_AUTO_PLAY, mPlayer.getPlayWhenReady()); outState.putInt(PARAM_WINDOW, mPlayer.getCurrentWindowIndex()); outState.putLong(PARAM_POSITION, mPlayer.getContentPosition()); } @Override public void onActivityStart() { if (Util.SDK_INT > 23) { createPlayer(isToPrepareOnResume); } } @Override public void onActivityResume() { if ((Util.SDK_INT <= 23 || mPlayer == null)) { createPlayer(isToPrepareOnResume); } } @Override public void onActivityPause() { if (Util.SDK_INT <= 23) { releasePlayer(); } } @Override public void onActivityStop() { if (Util.SDK_INT > 23) { releasePlayer(); } } @Override public void onActivityDestroy() { releaseAdsLoader(); } /** * ExoPlayerStatus interface methods */ @Override public boolean isPlayerCreated() { return mPlayer != null; } @Override public boolean isPlayerPrepared() { return isPlayerPrepared; } @Override public boolean isPlayerVideoMuted() { return isVideoMuted; } @Override public int getCurrentWindowIndex() { if (mPlayer != null) { return mPlayer.getCurrentWindowIndex(); } else { return 0; } } @Override public long getCurrentPosition() { if (mPlayer != null) { return mPlayer.getCurrentPosition(); } else { return 0; } } @Override public long getDuration() { if (mPlayer != null) { return mPlayer.getDuration(); } else { return 0; } } @Override public boolean isPlayingAd() { return mPlayer != null && mPlayer.isPlayingAd(); } /** * ExoPlayer Player.EventListener methods */ @Override public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { if (mExoPlayerListener != null) { mExoPlayerListener.onTracksChanged( mPlayer.getCurrentWindowIndex(), getNextWindowIndex(), mPlayer.getPlaybackState() == Player.STATE_READY); } } @Override public void onLoadingChanged(boolean isLoading) { onPlayerLoadingChanged(); if (mExoPlayerListener != null) { mExoPlayerListener.onLoadingStatusChanged(isLoading, mPlayer.getBufferedPosition(), mPlayer.getBufferedPercentage()); } } @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { if (mExoPlayerListener == null || mPlayer == null) { return; } switch (playbackState) { case Player.STATE_READY: if (playWhenReady) { onPlayerPlaying(); mExoPlayerListener.onPlayerPlaying(mPlayer.getCurrentWindowIndex()); } else { onPlayerPaused(); mExoPlayerListener.onPlayerPaused(mPlayer.getCurrentWindowIndex()); } break; case Player.STATE_BUFFERING: onPlayerBuffering(); mExoPlayerListener.onPlayerBuffering(mPlayer.getCurrentWindowIndex()); break; case Player.STATE_ENDED: mExoPlayerListener.onPlayerStateEnded(mPlayer.getCurrentWindowIndex()); break; case Player.STATE_IDLE: mExoPlayerListener.onPlayerStateIdle(mPlayer.getCurrentWindowIndex()); break; default: Log.e("ExoPlayerHelper-zaq", "onPlayerStateChanged unknown: " + playbackState); } } @Override public void onPlayerError(ExoPlaybackException e) { String errorString = null; switch (e.type) { case ExoPlaybackException.TYPE_SOURCE: //https://github.com/google/ExoPlayer/issues/2702 IOException ex = e.getSourceException(); String msg = ex.getMessage(); if (msg != null) { Log.e("ExoPlayerHelper", msg); errorString = msg; } break; case ExoPlaybackException.TYPE_RENDERER: Exception exception = e.getRendererException(); if (exception.getMessage() != null) { Log.e("ExoPlayerHelper", exception.getMessage()); } break; case ExoPlaybackException.TYPE_UNEXPECTED: RuntimeException runtimeException = e.getUnexpectedException(); Log.e("ExoPlayerHelper", runtimeException.getMessage() == null ? "Message is null" : runtimeException.getMessage()); if (runtimeException.getMessage() == null) { runtimeException.printStackTrace(); } errorString = runtimeException.getMessage(); break; case ExoPlaybackException.TYPE_OUT_OF_MEMORY: break; case ExoPlaybackException.TYPE_REMOTE: break; } if (e.type == ExoPlaybackException.TYPE_RENDERER) { Exception cause = e.getRendererException(); if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { // Special case for decoder initialization failures. MediaCodecRenderer.DecoderInitializationException decoderInitializationException = (MediaCodecRenderer.DecoderInitializationException) cause; if (decoderInitializationException.decoderName == null) { if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) { errorString = mContext.getString(R.string.error_querying_decoders); } else if (decoderInitializationException.secureDecoderRequired) { errorString = mContext.getString(R.string.error_no_secure_decoder, decoderInitializationException.mimeType); } else { errorString = mContext.getString(R.string.error_no_decoder, decoderInitializationException.mimeType); } } else { errorString = mContext.getString(R.string.error_instantiating_decoder, decoderInitializationException.decoderName); } } } if (errorString != null) { Log.e("ExoPlayerHelper", "errorString: " + errorString); } if (isBehindLiveWindow(e)) { createPlayer(true); Log.e("ExoPlayerHelper", "isBehindLiveWindow is true"); } if (mExoPlayerListener != null) { mExoPlayerListener.onPlayerError(errorString); } } private static boolean isBehindLiveWindow(ExoPlaybackException e) { if (e.type != ExoPlaybackException.TYPE_SOURCE) { return false; } Throwable cause = e.getSourceException(); while (cause != null) { if (cause instanceof BehindLiveWindowException) { return true; } cause = cause.getCause(); } return false; } @Override public void onRepeatModeChanged(int repeatMode) { } @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { } @Override public void onPositionDiscontinuity(int reason) { } @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { } @Override public void onSeekProcessed() { } @Override public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { } /** * ImaAdsLoader.VideoAdPlayerCallback */ @Override public void onPlay() { if (mExoAdListener != null) { mExoAdListener.onAdPlay(); } } @Override public void onVolumeChanged(int pI) { } @Override public void onLoaded() { } @Override public void onPause() { if (mExoAdListener != null) { mExoAdListener.onAdPause(); } if (mExoPlayerView != null) { mExoPlayerView.removeCallbacks(checkFreeze); } } @Override public void onResume() { if (mExoAdListener != null) { mExoAdListener.onAdResume(); } } @Override public void onEnded() { onAdEnded(); if (mExoAdListener != null) { mExoAdListener.onAdEnded(); } } @Override public void onError() { if (mExoAdListener != null) { mExoAdListener.onAdError(); } } @Override public void onBuffering() { if (mExoAdListener != null) { mExoAdListener.onBuffering(); } } /** * AdsMediaSource.MediaSourceFactory */ @Override public MediaSource createMediaSource(Uri uri) { return buildMediaSource(uri); } @Override public int[] getSupportedTypes() { return new int[]{C.TYPE_DASH, C.TYPE_HLS, C.TYPE_OTHER}; } /** * AdEvent.AdEventListener */ @Override public void onAdEvent(AdEvent adEvent) { if (mExoAdListener == null) { return; } switch (adEvent.getType()) { case TAPPED: mExoAdListener.onAdTapped(); break; case CLICKED: mExoAdListener.onAdClicked(); break; } } }