From 6287fbc7cb71bc6450bc876acdb2c441649a6650 Mon Sep 17 00:00:00 2001 From: volodymyr-bondarenko85 <131799099+volodymyr-bondarenko85@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:49:35 +0300 Subject: [PATCH] Codec failure retry timeout logic (#821) --- .../playkit/KDefaultRenderersFactory.java | 98 +++++++++++++++++++ .../main/java/com/kaltura/playkit/Player.java | 16 +++ .../playkit/player/ExoPlayerWrapper.java | 3 +- .../playkit/player/PlayerSettings.java | 22 +++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 playkit/src/main/java/com/kaltura/playkit/KDefaultRenderersFactory.java diff --git a/playkit/src/main/java/com/kaltura/playkit/KDefaultRenderersFactory.java b/playkit/src/main/java/com/kaltura/playkit/KDefaultRenderersFactory.java new file mode 100644 index 000000000..e6d2942af --- /dev/null +++ b/playkit/src/main/java/com/kaltura/playkit/KDefaultRenderersFactory.java @@ -0,0 +1,98 @@ +package com.kaltura.playkit; + +import android.content.Context; +import android.os.Handler; + +import androidx.annotation.NonNull; + +import com.kaltura.android.exoplayer2.DefaultRenderersFactory; +import com.kaltura.android.exoplayer2.ExoPlaybackException; +import com.kaltura.android.exoplayer2.PlaybackException; +import com.kaltura.android.exoplayer2.Renderer; +import com.kaltura.android.exoplayer2.mediacodec.MediaCodecSelector; +import com.kaltura.android.exoplayer2.video.MediaCodecVideoRenderer; +import com.kaltura.android.exoplayer2.video.VideoRendererEventListener; +import com.kaltura.playkit.player.PlayerSettings; + +import java.util.ArrayList; + +public class KDefaultRenderersFactory { + private static final PKLog log = PKLog.get("KDefaultRenderersFactory"); + + public static DefaultRenderersFactory createDecoderInitErrorRetryFactory( + Context context, + PlayerSettings playerSettings + ) { + return new DefaultRenderersFactory(context) { + @Override + protected void buildVideoRenderers(@NonNull Context context, + int extensionRendererMode, + @NonNull MediaCodecSelector mediaCodecSelector, + boolean enableDecoderFallback, + @NonNull Handler eventHandler, + @NonNull VideoRendererEventListener eventListener, + long allowedVideoJoiningTimeMs, + @NonNull ArrayList out) { + ArrayList renderersArrayList = new ArrayList<>(); + super.buildVideoRenderers(context, + extensionRendererMode, + mediaCodecSelector, + enableDecoderFallback, + eventHandler, + eventListener, + allowedVideoJoiningTimeMs, + renderersArrayList); + for (Renderer renderer : renderersArrayList) { + if (renderer instanceof MediaCodecVideoRenderer) { + out.add(new MediaCodecVideoRenderer( + context, + this.getCodecAdapterFactory(), + mediaCodecSelector, + allowedVideoJoiningTimeMs, + enableDecoderFallback, + eventHandler, + eventListener, + MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY) { + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + try { + super.render(positionUs, elapsedRealtimeUs); + } catch (ExoPlaybackException e) { + if (e.errorCode == PlaybackException.ERROR_CODE_DECODER_INIT_FAILED + && playerSettings.getCodecFailureRetryCount() > 0 && playerSettings.getCodecFailureRetryTimeout() > 0) { + for(int i = 0; i < playerSettings.getCodecFailureRetryCount(); i++) { + log.d("Retrying on coded init failure (" + i + ")"); + super.onReset(); + try { + Thread.sleep(playerSettings.getCodecFailureRetryTimeout()); + } catch (Exception e1) { + log.d("Interrupted while sleeping: " + e1.getMessage()); + e1.printStackTrace(); + } + try { + super.render(positionUs, elapsedRealtimeUs); + log.d("Retrying on coded init failure successful"); + // Stop retrying if no exception was thrown + break; + } catch (ExoPlaybackException e2) { + if (e2.errorCode != PlaybackException.ERROR_CODE_DECODER_INIT_FAILED + || i == playerSettings.getCodecFailureRetryCount() - 1) { + // Some other error happened or last retry. Throw exception to the caller + log.d("Codec init retry failed: " + e2.getMessage()); + throw e2; + } + } + } + } + } + } + } + ); + } else { + out.add(renderer); + } + } + } + }; + } +} diff --git a/playkit/src/main/java/com/kaltura/playkit/Player.java b/playkit/src/main/java/com/kaltura/playkit/Player.java index 3e1e06f1c..257994f6e 100644 --- a/playkit/src/main/java/com/kaltura/playkit/Player.java +++ b/playkit/src/main/java/com/kaltura/playkit/Player.java @@ -345,6 +345,22 @@ interface Settings { */ Settings setShutterStaysOnRenderedFirstFrame(boolean shutterStaysOnRenderedFirstFrame); + /** + * Set codecFailureRetryCount - count of the codec initialization failure retries + * + * @param codecFailureRetryCount + * @return - Player Settings + */ + Settings setCodecFailureRetryCount(int codecFailureRetryCount); + + /** + * Set codecFailureRetryTimeout - timeout to wait between retries of codec re-initialization on failure + * + * @param codecFailureRetryTimeout + * @return - Player Settings + */ + Settings setCodecFailureRetryTimeout(int codecFailureRetryTimeout); + /** * Set preference to choose internal subtitles over external subtitles (Only in the case if the same language is present * in both Internal and External subtitles) - Default is true (Internal is preferred) diff --git a/playkit/src/main/java/com/kaltura/playkit/player/ExoPlayerWrapper.java b/playkit/src/main/java/com/kaltura/playkit/player/ExoPlayerWrapper.java index 4a9b68bd8..e3756c6c3 100644 --- a/playkit/src/main/java/com/kaltura/playkit/player/ExoPlayerWrapper.java +++ b/playkit/src/main/java/com/kaltura/playkit/player/ExoPlayerWrapper.java @@ -83,6 +83,7 @@ import com.kaltura.android.exoplayer2.upstream.cache.CacheDataSource; import com.kaltura.android.exoplayer2.util.TimestampAdjuster; import com.kaltura.android.exoplayer2.video.ConfigurableLoadControl; +import com.kaltura.playkit.KDefaultRenderersFactory; import com.kaltura.playkit.LocalAssetsManager; import com.kaltura.playkit.LocalAssetsManagerExo; import com.kaltura.playkit.PKAbrFilter; @@ -248,7 +249,7 @@ private void initializePlayer() { } DefaultRenderersFactory renderersFactory = this.useSpeedAdjustingRenderer ? SpeedAdjustedRenderersFactory.createSpeedAdjustedRenderersFactory(context, playerSettings, exoPlayerView) - : new DefaultRenderersFactory(context); + : KDefaultRenderersFactory.createDecoderInitErrorRetryFactory(context, playerSettings); renderersFactory.setAllowedVideoJoiningTimeMs(playerSettings.getLoadControlBuffers().getAllowedVideoJoiningTimeMs()); renderersFactory.setEnableDecoderFallback(playerSettings.enableDecoderFallback()); diff --git a/playkit/src/main/java/com/kaltura/playkit/player/PlayerSettings.java b/playkit/src/main/java/com/kaltura/playkit/player/PlayerSettings.java index 61469315d..186a2257f 100644 --- a/playkit/src/main/java/com/kaltura/playkit/player/PlayerSettings.java +++ b/playkit/src/main/java/com/kaltura/playkit/player/PlayerSettings.java @@ -70,6 +70,8 @@ public class PlayerSettings implements Player.Settings { private PKRequestParams.Adapter contentRequestAdapter; private PKRequestParams.Adapter licenseRequestAdapter; private Object customLoadControlStrategy = null; + private int codecFailureRetryCount = -1; + private int codecFailureRetryTimeout = -1; public PKRequestParams.Adapter getContentRequestAdapter() { return contentRequestAdapter; @@ -179,6 +181,14 @@ public Object getCustomLoadControlStrategy() { return customLoadControlStrategy; } + public int getCodecFailureRetryCount() { + return codecFailureRetryCount; + } + + public int getCodecFailureRetryTimeout() { + return codecFailureRetryTimeout; + } + public boolean isTunneledAudioPlayback() { return isTunneledAudioPlayback; } @@ -395,6 +405,18 @@ public Player.Settings setCustomLoadControlStrategy(Object customLoadControlStra return this; } + @Override + public Player.Settings setCodecFailureRetryCount(int codecFailureRetryCount) { + this.codecFailureRetryCount = codecFailureRetryCount; + return this; + } + + @Override + public Player.Settings setCodecFailureRetryTimeout(int codecFailureRetryTimeout) { + this.codecFailureRetryTimeout = codecFailureRetryTimeout; + return this; + } + @Override public Player.Settings setTunneledAudioPlayback(boolean isTunneledAudioPlayback) { this.isTunneledAudioPlayback = isTunneledAudioPlayback;