在上一篇Android Exoplayer 實現多個音視頻文件混合播放以及音軌切換中我們提到一個問題,如果視頻和音頻時長不一致,特別是想混合多個音頻和多個視頻時就會出問題,無法播放。報錯如下:
E/ExoPlayerImplInternal(11191): Playback error
E/ExoPlayerImplInternal(11191): com.google.android.exoplayer2.ExoPlaybackException: Source error
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:684)
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:660)
E/ExoPlayerImplInternal(11191): at android.os.Handler.dispatchMessage(Handler.java:98)
E/ExoPlayerImplInternal(11191): at android.os.Looper.loop(Looper.java:136)
E/ExoPlayerImplInternal(11191): at android.os.HandlerThread.run(HandlerThread.java:61)
E/ExoPlayerImplInternal(11191): Caused by: com.google.android.exoplayer2.source.MergingMediaSource$IllegalMergeException
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.source.MergingMediaSource.onChildSourceInfoRefreshed(MergingMediaSource.java:252)
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.source.MergingMediaSource.onChildSourceInfoRefreshed(MergingMediaSource.java:52)
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.source.CompositeMediaSource.lambda$prepareChildSource$0$com-google-android-exoplayer2-source-CompositeMediaSource(CompositeMediaSource.java:120)
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.source.CompositeMediaSource$$ExternalSyntheticLambda0.onSourceInfoRefreshed(D8$$SyntheticClass:0)
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.source.BaseMediaSource.refreshSourceInfo(BaseMediaSource.java:94)
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.source.ConcatenatingMediaSource.updateTimelineAndScheduleOnCompletionActions(ConcatenatingMediaSource.java:746)
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.source.ConcatenatingMediaSource.handleMessage(ConcatenatingMediaSource.java:716)
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.source.ConcatenatingMediaSource.$r8$lambda$xvlxaabNVihM68DRWdn_WPenrXk(ConcatenatingMediaSource.java)
E/ExoPlayerImplInternal(11191): at com.google.android.exoplayer2.source.ConcatenatingMediaSource$$ExternalSyntheticLambda0.handleMessage(D8$$SyntheticClass:0)
E/ExoPlayerImplInternal(11191): ... 3 more
這個主要是播放時長不一致,無法同步時序導致。
使用場景:比如K歌應用中,沒有原版MV,只有音頻文件,想給音頻配一個背景視頻或多個混合視頻當MV,但視頻均是風景短片,時長與音頻不一致。當用Exoplayer進行混合播放時,我們希望以音頻時長為準,在音頻播放完成前,視頻循環播放。
一直沒有找到很好的方法解決,最后采取了一個笨辦法,啟用兩個播放器,一個專門播放視頻,一個專門播放音頻,這樣視頻任意混合或循環播放都與音頻互不干擾,就可用規避時序錯亂問題。
以下為實現樣例:
private ExoPlayer mExoPlayer,mExoPlayer2;//音頻播放器mExoPlayer = new ExoPlayer.Builder(context, renderersFactory).setTrackSelector(trackSelector).build();// 檢查音頻配置AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).setContentType(C.AUDIO_CONTENT_TYPE_MUSIC).build();mExoPlayer.setAudioAttributes(audioAttributes, true);//視頻播放器mExoPlayer2 = new ExoPlayer.Builder(context, new DefaultMediaSourceFactory(context)).setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT).build();DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(dataSourceFactory);// 創建兩個視頻的 MediaSourceMediaSource video1Source = mediaSourceFactory.createMediaSource(MediaItem.fromUri("asset://android_asset/01.mp4"));MediaSource video2Source = mediaSourceFactory.createMediaSource(MediaItem.fromUri("asset://android_asset/02.mp4"));ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(video1Source,video2Source);LoopingMediaSource loopingMediaSource = new LoopingMediaSource(concatenatingMediaSource);// 合并兩個音頻源MediaSource audio1Source = mediaSourceFactory.createMediaSource(MediaItem.fromUri("asset://android_asset/audio/ori.mp2"));MediaSource audio2Source = mediaSourceFactory.createMediaSource(MediaItem.fromUri("asset://android_asset/audio/acc.mp2"));MergingMediaSource audioMerged = new MergingMediaSource(audio1Source, audio2Source);mExoPlayer2.setMediaSource(loopingMediaSource);// mExoPlayer2.setRepeatMode(Player.REPEAT_MODE_ONE);// 添加到 ExoPlayermExoPlayer.setMediaSource(audioMerged);
需要注意的是兩個播放器要保持狀態同步,以播放進度音頻播放器為準。
@Overridepublic void prepareAsync() throws IllegalStateException {mExoPlayer.prepare();mExoPlayer2.prepare();}@Overridepublic void start() throws IllegalStateException {mExoPlayer.setPlayWhenReady(true);mExoPlayer2.setPlayWhenReady(true);// getCurrentPostion();}@Overridepublic void stop() throws IllegalStateException {mExoPlayer.stop();mExoPlayer2.stop();}@Overridepublic void pause() throws IllegalStateException {mExoPlayer.setPlayWhenReady(false);mExoPlayer2.setPlayWhenReady(false);}@Overridepublic void setSpeed(float speed) {PlaybackParameters parameters = new PlaybackParameters(speed);mExoPlayer.setPlaybackParameters(parameters);}@Overridepublic long getCurrentPosition() {if (mExoPlayer == null)return 0;return mExoPlayer.getCurrentPosition();}
還有就是切換音軌的時候需要注意,由于音視頻分開處理了,切換音軌的時候只處理音頻播放器即可,切換分辨率的時候只處理視頻播放器即可,這時媒體軌道數會比音視頻混合一起的情況要少一些,因為只有視頻或只有音頻軌道,切換時軌道索引值參數肯定要小些了。
比如上面樣例音頻播放器只有兩個音頻軌道,所以切換音軌時,索引只有0或1.
//原唱
TrackGroup selectedGroup = currentTrackGroups.get(0);
//伴奏
//TrackGroup selectedGroup = currentTrackGroups.get(1);// 應用新音軌mExoPlayer.setTrackSelectionParameters(mExoPlayer.getTrackSelectionParameters().buildUpon().setOverrideForType(new TrackSelectionOverride(selectedGroup, 0)) //需要切換到的音軌索引.build());
這樣就可以迂回解決多路不同時長音視頻混合流播放問題。大佬們有其他更好解決辦法的話歡飲留言交流。