项目名称:H5 直播
开发环境及项目依赖 安装包命令 npm install packagename
| 环境依赖 |
版本 |
说明 |
| Node |
12.6.1 |
开发基础环境, 内置npm包管理工具(npm可配置镜像版cnpm) |
| VueCli |
|
Vue 脚手架, 项目基础配置 |
| Vuex |
3.0.1 |
状态管理 |
| VueRouter |
3.0.3 |
Vue 路由 |
| Stylus |
0.54.5 |
CSS预处理器 |
| vant |
2.1.1 |
Vue - UI库 |
| vConsole |
3.3.4 |
端调试工具 |
| events |
3.1.0 |
js事件管理包 |
| amfe-flexible |
2.2.1 |
屏幕尺寸自适应 |
| postcss-pxtorem |
4.0.1 |
PostCSS的插件,用于将像素单元生成rem单位 |
| agora-rtc-sdk |
3.0.1 |
声网web - SDK |
| agora-rts |
1.0.0 |
声网H5 -SDK |
1 移动端适配
1.1 像素单位使用rem, vm, vh
1.2 使用 amfe-flexible 和 postcss-pxtorem 将px 转为 rem
配置如下:
1. main.ts/** rem config */import 'amfe-flexible'2. vue.config.jsconst autoprefixer = require('autoprefixer')const pxtorem = require('postcss-pxtorem')moudle.export = { css: { sourceMap: false, loaderOptions: { postcss: { plugins: [ autoprefixer(), pxtorem({ rootValue: 37.5, propList: ['*'] }) ] } } }}
2 创建RTS 工具类
import EventEmitter from 'events'import { AgoraRTS, AgoraRTC } from 'agora-rts'const appID = process.env.VUE_APP_AGORA_APP_APP_IDexport default class RTCClient { constructor() { this._client = null this._joined = false this._localStream = null this._params = {} this._uid = 0 this._eventBus = new EventEmitter() this._showProfile = false this._subscribed = false this._created = false } createClient(data) { this._client = AgoraRTC.createClient({ mode: data.mode, codec: data.codec }) if (isMobile()) { // 加载解码文件 AgoraRTS.init(AgoraRTC, { wasmDecoderPath: 'https://gcdncs.101.com/v0.1/static/ndrobot_rc_web/assets/AgoraRTS.wasm', asmDecoderPath: 'https://gcdncs.101.com/v0.1/static/ndrobot_rc_web/assets/AgoraRTS.asm' }).catch(e => { if (e === 'LOAD_DECODER_FAILED') { console.log('加载解码器失败!') } }) AgoraRTS.proxy(this._client) this._client.enableAudioVolumeIndicator() if (!AgoraRTS.checkSystemRequirements()) { alert('您的浏览器不支持 RTS!') } else { console.log('check success') } } return this._client } destroy() { this._created = false this._client = null } on(evt, callback) { const customEvents = ['localStream-added', 'screenShare-canceled'] if (customEvents.indexOf(evt) !== -1) { this._eventBus.on(evt, callback) return } this._client.on(evt, callback) } handleEvents() { this._client.on('error', err => { console.log(err) }) // Occurs when the peer user leaves the channel; for example, the peer user calls Client.leave. this._client.on('peer-leave', evt => { var id = evt.uid if (id !== this._params.uid) { // removeView(id) } console.log('peer-leave', id) }) // Occurs when the local stream is _published. this._client.on('stream-published', evt => { console.log('stream-published') }) // Occurs when the remote stream is added. this._client.on('stream-added', evt => { var remoteStream = evt.stream var id = remoteStream.getId() if (id !== this._params.uid) { this._client.subscribe(remoteStream, err => { console.log('stream subscribe failed', err) }) } console.log('stream-added remote-uid: ', id) }) // Occurs when a user subscribes to a remote stream. this._client.on('stream-subscribed', evt => { const remoteStream = evt.stream const id = remoteStream.getId() this._remoteStreams.push(remoteStream) // addView(id, this._showProfile) remoteStream.play('remote_video_' + id, { fit: 'cover' }) console.log('stream-subscribed remote-uid: ', id) }) // Occurs when the remote stream is removed; for example, a peer user calls Client.unpublish. this._client.on('stream-removed', evt => { const remoteStream = evt.stream const id = remoteStream.getId() remoteStream.stop('remote_video_' + id) this._remoteStreams = this._remoteStreams.filter(stream => { return stream.getId() !== id }) // removeView(id) console.log('stream-removed remote-uid: ', id) }) this._client.on('onTokenPrivilegeWillExpire', () => { // After requesting a new token // this._client.renewToken(token); console.log('onTokenPrivilegeWillExpire') }) this._client.on('onTokenPrivilegeDidExpire', () => { // After requesting a new token // client.renewToken(token); console.log('onTokenPrivilegeDidExpire') }) // Occurs when the live streaming starts. this._client.on('liveStreamingStarted', evt => { this._liveStreaming = true console.log('liveStreamingStarted', evt) }) // Occurs when the live streaming fails. this._client.on('liveStreamingFailed', evt => { console.log('liveStreamingFailed', evt) }) // Occurs when the live streaming stops. this._client.on('liveStreamingStopped', evt => { this._liveStreaming = false console.log('liveStreamingStopped', evt) }) // Occurs when the live transcoding setting is updated. this._client.on('liveTranscodingUpdated', evt => { console.log('liveTranscodingUpdated', evt) }) } setClientRole(role) { this._client.setClientRole(role) } createRTCStream(data) { return new Promise((resolve, reject) => { this._uid = this._localStream ? this._localStream.getId() : data.uid if (this._localStream) { this.unpublish() if (this._localStream.isPlaying()) { this._localStream.stop() } this._localStream.close() } // create rtc stream const rtcStream = AgoraRTC.createStream({ streamID: this._uid, audio: true, video: true, screen: false, microphoneId: data.microphoneId, cameraId: data.cameraId }) if (data.resolution && data.resolution !== 'default') { rtcStream.setVideoProfile(data.resolution) } // init local stream rtcStream.init( () => { this._localStream = rtcStream this._eventBus.emit('localStream-added', { stream: this._localStream }) if (data.muteVideo === false) { this._localStream.muteVideo() } if (data.muteAudio === false) { this._localStream.muteAudio() } this._localStream.play('home_local-stream') resolve() }, err => { // 检测 showNotSupport() reject(err) // Toast.error("stream init failed, please open console see more detail"); console.error('init local stream failed ', err) } ) }) } subscribe(stream, callback) { this._client.subscribe(stream, { video: true, audio: true }, callback) } getDevices() { return new Promise((resolve, reject) => { if (!this._client) this.createClient({ codec: 'h264', mode: 'live' }) this._createTmpStream().then(() => { AgoraRTC.getDevices(devices => { this._localStream.close() resolve(devices) }) }) }) } join(data) { this._joined = 'pending' return new Promise((resolve, reject) => { this._params = data // handle AgoraRTC client event this.handleEvents() // init client this._client.init( appID, () => { this._client.join( data.token ? data.token : null, data.channel, data.uid ? +data.uid : null, uid => { this._uid = uid this._joined = true data.uid = uid if (data.host) { this.createRTCStream(data) .then(() => { this.publish() resolve() }) .catch(err => { reject(err) }) } else { resolve() } }, err => { this._joined = false reject(err) console.error('client join failed', err) } ) }, err => { this._joined = false reject(err) console.error(err) } ) }) }// 发布流 publish() { this._client.publish(this._localStream, err => { console.error(err) }) }// 取消发布 unpublish() { if (!this._client) { return } this._client.unpublish(this._localStream, err => { console.error(err) }) }// 关播 退出方法 leave() { return new Promise(resolve => { // leave channel this._client.leave( () => { this._joined = false this.destroy() if (this._localStream && this._localStream.isPlaying()) { document .querySelector(`#player_${this._localStream.getId()}`) .remove() this._localStream.stop() } resolve() }, err => { console.log('channel leave failed') console.error(err) } ) }) }}/** tools 后面会单独抽离工具方法 */// 判断不兼容的浏览器const showNotSupport = () => { var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream var isAndroid = navigator.userAgent.toLowerCase().indexOf('android') > -1 var nativeBrowserName = '系统浏览器' if (isIOS) { nativeBrowserName = 'Safari' } if (isAndroid) { nativeBrowserName = 'Chrome' } console.log(nativeBrowserName) console.log(location.href)}// 判断是否在移动端环境const isMobile = () => { return /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)}
3 直播步骤(先申请好临时token, 房间号)
3.1 直播步骤( 以下方法通过RTS工具类封装过)
3.1.1 创建Client
this.client = new RTCClient()this.client.createClient({ codec: 'h264', mode: 'live' })
| 参数名称 |
说明 |
示例 |
| mode |
使用场景 |
live |
| codec |
编码格式 |
h264 |
3.2.2 加入房间
this.client.join(this.options)
| 参数名称 |
说明 |
示例 |
| options |
|
详细内部参数如下 |
| uid |
用户uid |
001 |
| channel |
房间名称 |
ealien |
| token |
临时token与房间号绑定 |
asdasd |
| host |
是否是主播 |
true |
3.2.3 监听回调
// 远端流加入触发this.client.on('stream-added', evt => { const remoteStream = evt.stream // 订阅远端流 this.client.subscribe(remoteStream, err => { console.log(err) }) }) // 远端流订阅成功 this.client.on('stream-subscribed', evt => { const remoteStream = evt.stream // 播放流 remoteStream.play('live_remote-stream', err => { console.log(err) }) }) // 远端流移除 this.client.on('stream-removed', e => { var stream = e.stream const id = stream.id // 删除视频容器 id && document.querySelector(`#player-${id}`).remove() }) // 远端用户加入触发 this.client.on('peer-online', function(evt) { console.log('peer-online YOU 新用户加入', evt.uid) }) // 远端流 this.client.on('peer-leave', e => { console.log('通知 主播离开了' + e.uid) const uid = e.uid const reason = e.reason console.log('remote user left ', uid, 'reason: ', reason) })