上图为拉流页面(即看直播页面)
最近刚刚做完直播项目,来总结一下,直播使用七牛云直播实现的,直播和看直播写了两个Activiy,公用同一个Fragment,大致结构是这样的。下面先来说主播页面(前期引入sak省略):
CameraPreviewFrameView cameraPreviewFrameView =
(CameraPreviewFrameView) findViewById(R.id.cameraPreview_surfaceView);
cameraPreviewFrameView.setListener(this);
streamingProfile = new StreamingProfile();
try {
// // audio sample rate is 44100, audio bitrate is 96 * 1024 bps
// StreamingProfile.AudioProfile aProfile = new StreamingProfile.AudioProfile(44100, 96 * 1024);
//// fps is 30, video bitrate is 1000 * 1024 bps, maxKeyFrameInterval is 48
// StreamingProfile.VideoProfile vProfile = new StreamingProfile.VideoProfile(30, 1000 * 1024, 48);
// StreamingProfile.AVProfile avProfile = new StreamingProfile.AVProfile(vProfile, aProfile);
streamingProfile
// .setAVProfile(avProfile)
.setVideoQuality(StreamingProfile.VIDEO_QUALITY_MEDIUM2)//视频的 fps 为 30,码率为 1000 kbps
.setAudioQuality(StreamingProfile.AUDIO_QUALITY_MEDIUM2)//音频的采样率为 44100 HZ,码率为 48 kbps。
.setEncodingSizeLevel(StreamingProfile.VIDEO_ENCODING_HEIGHT_480)//使用内置的 encoding size level
.setBitrateAdjustMode(StreamingProfile.BitrateAdjustMode.Auto)
// .setAudioQuality(StreamingProfile.AUDIO_QUALITY_LOW1)
// .setPreferredVideoEncodingSize(960, 544)
//.setEncodingSizeLevel(StreamingProfile.VIDEO_ENCODING_HEIGHT_480)
.setEncoderRCMode(StreamingProfile.EncoderRCModes.QUALITY_PRIORITY)//质量优先,实际的码率可能高于设置的码率
// .setAVProfile(avProfile)
// .setDnsManager(getMyDnsManager())
// .setAdaptiveBitrateEnable(true)
.setFpsControllerEnable(true)
.setStreamStatusConfig(new StreamingProfile.StreamStatusConfig(3))
.setPublishUrl(urlPush)//推流url
// .setEncodingOrientation(StreamingProfile.ENCODING_ORIENTATION.PORT)
.setSendingBufferProfile(new StreamingProfile.SendingBufferProfile(0.2f, 0.8f, 3.0f, 20 * 1000));
CameraStreamingSetting setting = new CameraStreamingSetting();
setting.setCameraId(Camera.CameraInfo.CAMERA_FACING_BACK)
.setContinuousFocusModeEnabled(true)
// FaceBeautySetting 中的参数依次为:beautyLevel,whiten,redden,即磨皮程度、美白程度以及红润程度,取值范围为[0.0f, 1.0f]
.setFaceBeautySetting(new CameraStreamingSetting.FaceBeautySetting(1.0f, 1.0f, 0.8f))
.setVideoFilter(CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_BEAUTY)
.setCameraPrvSizeLevel(CameraStreamingSetting.PREVIEW_SIZE_LEVEL.LARGE)
.setCameraPrvSizeRatio(CameraStreamingSetting.PREVIEW_SIZE_RATIO.RATIO_16_9);
// streamingManager = new MediaStreamingManager(this, cameraPreview_afl, cameraPreviewFrameView,
// AVCodecType.HW_VIDEO_WITH_HW_AUDIO_CODEC);// 视频硬编,音频硬编
streamingManager = new MediaStreamingManager(this, cameraPreviewFrameView,
AVCodecType.HW_VIDEO_WITH_HW_AUDIO_CODEC);// 视频硬编,音频硬编
// hw codec // soft codec
mMicrophoneStreamingSetting = new MicrophoneStreamingSetting();
streamingManager.setStreamingSessionListener(this);
mMicrophoneStreamingSetting.setBluetoothSCOEnabled(false);//蓝牙麦克风支持关闭
streamingManager.prepare(setting, null, null, streamingProfile);
streamingManager.setStreamingStateListener(this);
streamingManager.setStreamStatusCallback(this);
} catch (URISyntaxException e) {
e.printStackTrace();
}
备注也是很清晰明了。
下面是直播的一些状态:
@Override
public void onStateChanged(StreamingState streamingState, Object o) {
switch (streamingState) {
case PREPARING:
break;
case READY:
Log.e("TAGresponse", "READY" + " ");
// start streaming when READY
new Thread(new Runnable() {
@Override
public void run() {
if (streamingManager != null) {
streamingManager.startStreaming();
}
}
}).start();
break;
case CONNECTING:
Log.e("TAGresponse", "CONNECTING" + " ");
currntzoom = streamingManager.getZoom();
maxZoom = streamingManager.getMaxZoom();
seekBar.setMax(maxZoom);
seekBar.setProgress(currntzoom);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
currntzoom = progress;
if (streamingManager.isZoomSupported()) {
streamingManager.setZoomValue(currntzoom);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
ArticleLvbState_v2();
break;
case STREAMING:
Log.e("TAGresponse", "STREAMING" + " ");
// The av packet had been sent.
break;
case SHUTDOWN:
Log.e("TAGresponse", "SHUTDOWN" + " ");
// The streaming had been finished.
break;
case IOERROR:
Log.e("TAGresponse", "IOERROR" + " ");
// streamingManager.stopStreaming();
// finish();
if (NetWorkCheckTool.isNetworkAvailable(this)) {
showNetWorkDialog("当前流中断,是否退出", "确定", true);
} else {
showNetWorkDialog("当前网络中断,请重新连接", "重连", false);
}
// Network connect error.
break;
case SENDING_BUFFER_EMPTY:
Log.e("TAGresponse", "SENDING_BUFFER_EMPTY" + " ");
break;
case SENDING_BUFFER_FULL:
Log.e("TAGresponse", "SENDING_BUFFER_FULL" + " ");
break;
case AUDIO_RECORDING_FAIL:
Log.e("TAGresponse", "AUDIO_RECORDING_FAIL" + " ");
// Failed to record audio.
break;
case OPEN_CAMERA_FAIL:
Log.e("TAGresponse", "OPEN_CAMERA_FAIL" + " ");
// Failed to open camera.
break;
case DISCONNECTED:
Log.e("TAGresponse", "DISCONNECTED" + " ");
// streamingManager.stopStreaming();
// finish();
if (NetWorkCheckTool.isNetworkAvailable(this)) {
// RetrieveLvb();
} else {
}
// The socket is broken while streaming
break;
case TORCH_INFO://
break;
}
}
当断网或是其他错误会走IOERROR,这是会判断是否联网,未联网的话会提示网络连接中断,是否重新连接,这里会请求服务器进行重连,若网络连接成功并且服务器返回重连url的话会进行重新推流,若服务器返回当前推流中断,则提示主播是否要退出直播;若网络连接没问题,则直接提示主播当前流中断,是否退出直播。
剩下的就是fragment页面逻辑(关注,聊天、禁言、切换摄像头、静音等等)
下面就是观看直播页面
View loadingView = findViewById(R.id.LoadingView);
mVideoView.setOnErrorListener(this);
mVideoView.setOnCompletionListener(this);
mVideoView.setBufferingIndicator(loadingView);
mVideoView.setVideoPath(pull);
mVideoView.setDisplayAspectRatio(PLVideoView.ASPECT_RATIO_PAVED_PARENT);
mVideoView.setOnCompletionListener(this);
mVideoView.setCoverView(loadingView);
mVideoView.start();
代码很简单,这样就实现了观看直播。
@Override
public boolean onError(int i) {
// if(i == -875574520){
// Toast.makeText(this, "直播异常请稍后", Toast.LENGTH_SHORT).show();
// finish();
// return true;
// }
Log.i(TAG, "onError: " + i);
// mVideoView.setVideoPath(pull);
// mVideoView.start();
switch (i) {
case ERROR_CODE_IO_ERROR://网络异常
if (NetWorkCheckTool.isNetworkAvailable(this)) {
if (!TextUtils.isEmpty(errorCode) && errorCode.equals("406")) {
} else {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
GetStreamState();
}
}, 2000);
}
} else {//
showNetWorkDialog("网络已断开,请重新连接", "重连", false);
}
break;
case MEDIA_ERROR_UNKNOWN://未知错误
break;
case ERROR_CODE_OPEN_FAILED://播放器打开失败
break;
case ERROR_CODE_SEEK_FAILED://拖动失败
break;
case ERROR_CODE_HW_DECODE_FAILURE://硬解失败
break;
case ERROR_CODE_PLAYER_DESTROYED://播放器已被销毁,需要再次 setVideoURL 或 prepareAsync
break;
case ERROR_CODE_PLAYER_VERSION_NOT_MATCH://so 库版本不匹配,需要升级
break;
case ERROR_CODE_PLAYER_CREATE_AUDIO_FAILED://AudioTrack 初始化失败,可能无法播放音频
break;
}
return true;
}
这是观看直播的一个状态,上面备注 很清晰了,这样拉流页面也实现了,
剩下的fragment页面逻辑,改天有时间再更新,由于时间问题,暂时先写到这,有问题欢迎留言。