Appearance
Android 端使用七牛霹雳直播云服务实现直播。
七牛服务器配置
在七牛直播云服务创建一个直播空间,按需配置。
在 PILI 直播云服务创建配置上面创建的直播空间,由于 Android SDK 1.0 不支持动态鉴权,这里先配置为静态鉴权。
SDK 安装
官网下载 SDK 压缩包,PLDroidMediaStreaming 和 PLDroidPlayer,解压后可分别见4个文件夹 arm64-v8a
armeabi
armeabi-v7a
x86
和一个 jar 文件。
把 jar 文件复制到 app/libs
把4个文件夹复制到 app/src/main/java/jniLibs
添加其他依赖
groovy
compile 'com.qiniu:happy-dns:0.2.7'
compile 'com.qiniu.pili:pili-android-qos:0.8.20'
配置权限
xml
<uses-feature android:glEsVersion="0x00020000"
android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
推流
java
public class SWCameraStreamingActivity extends Activity
implements StreamingStateChangedListener {
private JSONObject mJSONObject;
private MediaStreamingManager mCameraStreamingManager;
private StreamingProfile mProfile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera_streaming);
AspectFrameLayout afl = (AspectFrameLayout) findViewById(R.id.cameraPreview_afl);
afl.setShowMode(AspectFrameLayout.SHOW_MODE.REAL);
GLSurfaceView glSurfaceView = (GLSurfaceView) findViewById(R.id.cameraPreview_surfaceView);
String publishUrl = getIntent().getStringExtra("publish_url");
try {
mProfile = new StreamingProfile();
mProfile.setVideoQuality(StreamingProfile.VIDEO_QUALITY_HIGH1)
.setAudioQuality(StreamingProfile.AUDIO_QUALITY_MEDIUM2)
.setEncodingSizeLevel(StreamingProfile.VIDEO_ENCODING_HEIGHT_480)
.setEncoderRCMode(StreamingProfile.EncoderRCModes.QUALITY_PRIORITY)
.setPublishUrl(publishUrl);
CameraStreamingSetting setting = new CameraStreamingSetting();
setting.setCameraId(Camera.CameraInfo.CAMERA_FACING_BACK)
.setContinuousFocusModeEnabled(true)
.setCameraPrvSizeLevel(CameraStreamingSetting.PREVIEW_SIZE_LEVEL.MEDIUM)
.setCameraPrvSizeRatio(CameraStreamingSetting.PREVIEW_SIZE_RATIO.RATIO_16_9);
mCameraStreamingManager = new MediaStreamingManager(this, afl, glSurfaceView,
AVCodecType.SW_VIDEO_WITH_SW_AUDIO_CODEC);
mCameraStreamingManager.prepare(setting, mProfile);
mCameraStreamingManager.setStreamingStateListener(this);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
@Override
protected void onResume() {
super.onResume();
mCameraStreamingManager.resume();
}
@Override
protected void onPause() {
super.onPause();
// You must invoke pause here.
mCameraStreamingManager.pause();
}
@Override
public void onStateChanged(StreamingState state, Object info) {
switch (state) {
case PREPARING:
break;
case READY:
// start streaming when READY
new Thread(new Runnable() {
@Override
public void run() {
if (mCameraStreamingManager != null) {
mCameraStreamingManager.startStreaming();
}
}
}).start();
break;
case CONNECTING:
break;
case STREAMING:
// The av packet had been sent.
break;
case SHUTDOWN:
// The streaming had been finished.
break;
case IOERROR:
// Network connect error.
break;
case SENDING_BUFFER_EMPTY:
break;
case SENDING_BUFFER_FULL:
break;
case AUDIO_RECORDING_FAIL:
// Failed to record audio.
break;
case OPEN_CAMERA_FAIL:
// Failed to open camera.
break;
case DISCONNECTED:
// The socket is broken while streaming
break;
}
}
}
播放
java
PLVideoView vv = (PLVideoView) findViewById(R.id.PLVideoView);
vv.setVideoPath("rtmp://pili-live-rtmp.sb.live.mothin.com/mothin/st2");
vv.start();
xml
<com.pili.pldroid.player.widget.PLVideoView
.../>
应用服务器
js
const express = require('express');
const pili = require('piliv2');
const config = require('../config');
const hash = require('../util/hash');
const router = express.Router();
const credentials = new pili.Credentials(config.qiniu.accessKey, config.qiniu.secretKey);
const HUB = 'mothin';
pili.config.API_HOST = 'pili.qiniuapi.com';
const publishDomain = 'pili-publish.sb.live.mothin.com';
const hub = new pili.Hub(credentials, HUB);
router.get('/publish-url/:stream', function (req, res, next) {
const publishKey = hub.newStream(req.params.stream).credentials.publishKey;
res.send(publishURL(publishDomain, HUB, req.params.stream, 60, publishKey));
});
router.get('/new-stream/:stream', function (req, res, next) {
res.send(hub.newStream(req.params.stream));
});
module.exports = router;
function publishURL(domain, hub, key, expireAfterSec, publishKey) {
var expire = Math.floor(Date.now() / 1000) + expireAfterSec;
var path = `/${hub}/${key}?expire=${expire}`
var token = sign(path, publishKey);
return `rtmp://${domain}${path}&token=${token}`;
}
function sign(data, publishKey) {
var digest = hash.hmacSha1(data, publishKey);
var sageDigest = hash.base64ToUrlSafe(digest);
return sageDigest;
}
// var pu = Pili.publishURL(credentials, 'publish-rtmp.sb.live.mothin.com', HUB, 'streamkey', 60);
// var rtmpURL = Pili.rtmpPlayURL('live-rtmp.sb.live.mothin.com', HUB, 'streamkey');
// var hdlURL = Pili.hdlPlayURL('live-rtmp.sb.live.mothin.com', HUB, 'streamkey');
// var hlsURL = Pili.hlsPlayURL('live-rtmp.sb.live.mothin.com', HUB, 'streamkey');
// var snapURL = Pili.snapshotPlayURL('live-rtmp.sb.live.mothin.com', HUB, 'streamkey');