方式一:
1、通常一种切换码流方式,如系统播放器切换码流,先Stop,再Create Player,再onPrepare,再Start
方式二: 2、实际上还有一种无缝切换码流,如果是单实例播放器,点击切换码流时,不销毁播放器器,只是暂停解码。开始请求新的url中数据,比如下载了有1-2片TS流后,重新送给解码器,重新启动解码,然后渲染输出。达到无缝输出的效果。
方式三: 3、如果是多实例播放器,总的实现思路是:用两个MediaPlayer 两个 SurfaceView.
首先让一个Mediaplayer 播一个视频,一般播一个小视频,这样不占资源,这个MediaPlayer播放的时候,让它Stop在这里,可以让这个MediaPlayer 隐藏掉 ,注意不要Reset或者Release这个MediaPlayer 。然后可以让另外一个MediaPlayer 播放视频, 这个时候这个MediaPlayer 切换视频资源的时候就不会出现黑屏的现象。达到无缝效果,至于之间播放到哪个位置,很简单,可以用存储起来,另外一个MediaPlayer播放时,如果有去取下这个数据,起播后,SeekTo到对应位置。然后Start。一样是无缝。
方式四: 4、以前有参考过一个专利,思路是这样的(实际也是用了多实例):
视频流采集终端收到用户终端的规格切换请求后,保持原编码器实例继续运行,并启动一个新编码器实例;再将新视频流与原视频流进行帧号同步;然后在新视频流中选择一个关键帧,并从该关键帧开始向用户终端传送新视频流,原视频流传送完该关键帧的上一帧数据后结束,且该关键帧的帧号与原视频流的最后一个关键帧的帧号之间的间距大于新视频流GOP长度的1/2;然后再关闭原编码器实例。
具体步骤:
a、保持原编码器实例继续运行,并按用户终端所请求的新规格启动一个新编码器实例,其中的原编码器实例是指用户终端上一次所请求的原规格的编码器实例;
b、将新视频流与原视频流进行帧号同步,使该两个视频流中的各个相同内容的帧一一对应;其中,新视频流是指新编码器实例输出的视频流,原视频流是指原编码器实例输出的视频流;
c、在新视频流中选择一个关键帧,并从该关键帧开始向用户终端传送新视频流,原视频流传送完该关键帧的上一帧数据后结束,且该关键帧的帧号与原视频流的最后一个关键帧的帧号之间的间距大于新视频流GOP长度的1/2;
d、关闭原编码器实例,空出编码器硬件资源,准备下一次切换。
举例 以方式3为例,我们来做个简单Demo实验实现无缝切码流
首先看下切换效果图(两个视频一个是在打球,一个是录风景):
package com.example.hejunlin.seamlessvideo; import android.annotation.TargetApi; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Gravity; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.FrameLayout; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; /** * 兼容8.0版本 */ public class MainActivity extends AppCompatActivity { private SurfaceView mVideoSurface, mNextSurface; private FrameLayout mFrame; private MediaPlayer mCurrentMediaPlayer, mNextMediaPlayer; private Handler mHandler; private int mIndex = 0; private String path1 = Environment.getExternalStorageDirectory().getAbsolutePath() + "/1.mp4"; private String path2 = Environment.getExternalStorageDirectory().getAbsolutePath() + "/2.mp4"; private String[] paths = new String[]{path1, path2}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!checkPermission()) { startAction(); } else { if (checkPermission()) { requestPermissionAndContinue(); } else { startAction(); } } } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacks(mPlayRun); if (mCurrentMediaPlayer != null) { mCurrentMediaPlayer.release(); mCurrentMediaPlayer = null; } if (mNextMediaPlayer != null) { mNextMediaPlayer.release(); mNextMediaPlayer = null; } } Runnable mPlayRun = new Runnable() { @Override public void run() { Log.d(MainActivity.class.getSimpleName(), "run: "); mCurrentMediaPlayer.pause(); mNextMediaPlayer.pause(); mNextMediaPlayer.reset(); try { if (mIndex == 0) { String path = paths[mIndex % paths.length]; Log.d(MainActivity.class.getSimpleName(), "path1: " + path); mIndex++; mCurrentMediaPlayer.setDataSource(MainActivity.this, Uri.parse(path)); mCurrentMediaPlayer.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer player) { Log.d(MainActivity.class.getSimpleName(), "start 1"); mCurrentMediaPlayer.start(); mVideoSurface.setVisibility(View.GONE); } }); mCurrentMediaPlayer.prepareAsync(); mNextMediaPlayer.setDataSource(MainActivity.this, Uri.parse(path)); mNextMediaPlayer.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer arg0) { mNextMediaPlayer.start(); } }); mNextMediaPlayer.prepareAsync(); } else { String path = paths[mIndex % paths.length]; mIndex++; Log.d(MainActivity.class.getSimpleName(), "path2: " + path); mNextMediaPlayer.setDataSource(MainActivity.this, Uri.parse(path)); mNextMediaPlayer.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer arg0) { mNextMediaPlayer.start(); Log.d(MainActivity.class.getSimpleName(), "start 2"); } }); mNextMediaPlayer.prepareAsync(); } } catch (Exception e) { e.printStackTrace(); } mHandler.postDelayed(mPlayRun, 10000); // 第一个视频10s钟 } }; class VideoSurfaceHodlerCallback implements SurfaceHolder.Callback { @Override public void surfaceChanged( SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { mCurrentMediaPlayer.setDisplay(mVideoSurface.getHolder()); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } } class NextVideoSurfaceHodlerCallback implements SurfaceHolder.Callback { @Override public void surfaceChanged( SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { mNextMediaPlayer.setDisplay(mNextSurface.getHolder()); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } } private static final int PERMISSION_REQUEST_CODE = 200; private boolean checkPermission() { return ContextCompat.checkSelfPermission(this, WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ; } private void requestPermissionAndContinue() { if (ContextCompat.checkSelfPermission(this, WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, WRITE_EXTERNAL_STORAGE) && ActivityCompat.shouldShowRequestPermissionRationale(this, READ_EXTERNAL_STORAGE)) { AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this); alertBuilder.setCancelable(true); alertBuilder.setTitle("权限申请"); alertBuilder.setMessage("获取对应权限"); alertBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public void onClick(DialogInterface dialog, int which) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{WRITE_EXTERNAL_STORAGE , READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); } }); AlertDialog alert = alertBuilder.create(); alert.show(); Log.e("", "permission denied, show dialog"); } else { ActivityCompat.requestPermissions(MainActivity.this, new String[]{WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); } } else { startAction(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSION_REQUEST_CODE) { if (permissions.length > 0 && grantResults.length > 0) { boolean flag = true; for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { flag = false; } } if (flag) { startAction(); } else { finish(); } } else { finish(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } private void startAction() { mFrame = new FrameLayout(this); setContentView(mFrame); mHandler = new Handler(); mCurrentMediaPlayer = new MediaPlayer(); mNextMediaPlayer = new MediaPlayer(); mVideoSurface = new SurfaceView(this); mVideoSurface.getHolder().addCallback(new VideoSurfaceHodlerCallback()); mNextSurface = new SurfaceView(this); mNextSurface.getHolder().addCallback(new NextVideoSurfaceHodlerCallback()); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( 1080, 1920); lp.gravity = Gravity.LEFT | Gravity.TOP; mVideoSurface.setLayoutParams(lp); lp = new FrameLayout.LayoutParams( 1080, 1920); lp.gravity = Gravity.LEFT | Gravity.TOP; mNextSurface.setLayoutParams(lp); mFrame.addView(mNextSurface); mFrame.addView(mVideoSurface); mHandler.postDelayed(mPlayRun, 0); } }
工程源码公号后台回复‘无缝切码流’关键字,获取。