项目名称: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. 1. main.ts
  2. /** rem config */
  3. import 'amfe-flexible'
  4. 2. vue.config.js
  5. const autoprefixer = require('autoprefixer')
  6. const pxtorem = require('postcss-pxtorem')
  7. moudle.export = {
  8. css: {
  9. sourceMap: false,
  10. loaderOptions: {
  11. postcss: {
  12. plugins: [
  13. autoprefixer(),
  14. pxtorem({
  15. rootValue: 37.5,
  16. propList: ['*']
  17. })
  18. ]
  19. }
  20. }
  21. }
  22. }

2 创建RTS 工具类

  1. import EventEmitter from 'events'
  2. import { AgoraRTS, AgoraRTC } from 'agora-rts'
  3. const appID = process.env.VUE_APP_AGORA_APP_APP_ID
  4. export default class RTCClient {
  5. constructor() {
  6. this._client = null
  7. this._joined = false
  8. this._localStream = null
  9. this._params = {}
  10. this._uid = 0
  11. this._eventBus = new EventEmitter()
  12. this._showProfile = false
  13. this._subscribed = false
  14. this._created = false
  15. }
  16. createClient(data) {
  17. this._client = AgoraRTC.createClient({
  18. mode: data.mode,
  19. codec: data.codec
  20. })
  21. if (isMobile()) {
  22. // 加载解码文件
  23. AgoraRTS.init(AgoraRTC, {
  24. wasmDecoderPath:
  25. 'https://gcdncs.101.com/v0.1/static/ndrobot_rc_web/assets/AgoraRTS.wasm',
  26. asmDecoderPath:
  27. 'https://gcdncs.101.com/v0.1/static/ndrobot_rc_web/assets/AgoraRTS.asm'
  28. }).catch(e => {
  29. if (e === 'LOAD_DECODER_FAILED') {
  30. console.log('加载解码器失败!')
  31. }
  32. })
  33. AgoraRTS.proxy(this._client)
  34. this._client.enableAudioVolumeIndicator()
  35. if (!AgoraRTS.checkSystemRequirements()) {
  36. alert('您的浏览器不支持 RTS!')
  37. } else {
  38. console.log('check success')
  39. }
  40. }
  41. return this._client
  42. }
  43. destroy() {
  44. this._created = false
  45. this._client = null
  46. }
  47. on(evt, callback) {
  48. const customEvents = ['localStream-added', 'screenShare-canceled']
  49. if (customEvents.indexOf(evt) !== -1) {
  50. this._eventBus.on(evt, callback)
  51. return
  52. }
  53. this._client.on(evt, callback)
  54. }
  55. handleEvents() {
  56. this._client.on('error', err => {
  57. console.log(err)
  58. })
  59. // Occurs when the peer user leaves the channel; for example, the peer user calls Client.leave.
  60. this._client.on('peer-leave', evt => {
  61. var id = evt.uid
  62. if (id !== this._params.uid) {
  63. // removeView(id)
  64. }
  65. console.log('peer-leave', id)
  66. })
  67. // Occurs when the local stream is _published.
  68. this._client.on('stream-published', evt => {
  69. console.log('stream-published')
  70. })
  71. // Occurs when the remote stream is added.
  72. this._client.on('stream-added', evt => {
  73. var remoteStream = evt.stream
  74. var id = remoteStream.getId()
  75. if (id !== this._params.uid) {
  76. this._client.subscribe(remoteStream, err => {
  77. console.log('stream subscribe failed', err)
  78. })
  79. }
  80. console.log('stream-added remote-uid: ', id)
  81. })
  82. // Occurs when a user subscribes to a remote stream.
  83. this._client.on('stream-subscribed', evt => {
  84. const remoteStream = evt.stream
  85. const id = remoteStream.getId()
  86. this._remoteStreams.push(remoteStream)
  87. // addView(id, this._showProfile)
  88. remoteStream.play('remote_video_' + id, { fit: 'cover' })
  89. console.log('stream-subscribed remote-uid: ', id)
  90. })
  91. // Occurs when the remote stream is removed; for example, a peer user calls Client.unpublish.
  92. this._client.on('stream-removed', evt => {
  93. const remoteStream = evt.stream
  94. const id = remoteStream.getId()
  95. remoteStream.stop('remote_video_' + id)
  96. this._remoteStreams = this._remoteStreams.filter(stream => {
  97. return stream.getId() !== id
  98. })
  99. // removeView(id)
  100. console.log('stream-removed remote-uid: ', id)
  101. })
  102. this._client.on('onTokenPrivilegeWillExpire', () => {
  103. // After requesting a new token
  104. // this._client.renewToken(token);
  105. console.log('onTokenPrivilegeWillExpire')
  106. })
  107. this._client.on('onTokenPrivilegeDidExpire', () => {
  108. // After requesting a new token
  109. // client.renewToken(token);
  110. console.log('onTokenPrivilegeDidExpire')
  111. })
  112. // Occurs when the live streaming starts.
  113. this._client.on('liveStreamingStarted', evt => {
  114. this._liveStreaming = true
  115. console.log('liveStreamingStarted', evt)
  116. })
  117. // Occurs when the live streaming fails.
  118. this._client.on('liveStreamingFailed', evt => {
  119. console.log('liveStreamingFailed', evt)
  120. })
  121. // Occurs when the live streaming stops.
  122. this._client.on('liveStreamingStopped', evt => {
  123. this._liveStreaming = false
  124. console.log('liveStreamingStopped', evt)
  125. })
  126. // Occurs when the live transcoding setting is updated.
  127. this._client.on('liveTranscodingUpdated', evt => {
  128. console.log('liveTranscodingUpdated', evt)
  129. })
  130. }
  131. setClientRole(role) {
  132. this._client.setClientRole(role)
  133. }
  134. createRTCStream(data) {
  135. return new Promise((resolve, reject) => {
  136. this._uid = this._localStream ? this._localStream.getId() : data.uid
  137. if (this._localStream) {
  138. this.unpublish()
  139. if (this._localStream.isPlaying()) {
  140. this._localStream.stop()
  141. }
  142. this._localStream.close()
  143. }
  144. // create rtc stream
  145. const rtcStream = AgoraRTC.createStream({
  146. streamID: this._uid,
  147. audio: true,
  148. video: true,
  149. screen: false,
  150. microphoneId: data.microphoneId,
  151. cameraId: data.cameraId
  152. })
  153. if (data.resolution && data.resolution !== 'default') {
  154. rtcStream.setVideoProfile(data.resolution)
  155. }
  156. // init local stream
  157. rtcStream.init(
  158. () => {
  159. this._localStream = rtcStream
  160. this._eventBus.emit('localStream-added', {
  161. stream: this._localStream
  162. })
  163. if (data.muteVideo === false) {
  164. this._localStream.muteVideo()
  165. }
  166. if (data.muteAudio === false) {
  167. this._localStream.muteAudio()
  168. }
  169. this._localStream.play('home_local-stream')
  170. resolve()
  171. },
  172. err => {
  173. // 检测
  174. showNotSupport()
  175. reject(err)
  176. // Toast.error("stream init failed, please open console see more detail");
  177. console.error('init local stream failed ', err)
  178. }
  179. )
  180. })
  181. }
  182. subscribe(stream, callback) {
  183. this._client.subscribe(stream, { video: true, audio: true }, callback)
  184. }
  185. getDevices() {
  186. return new Promise((resolve, reject) => {
  187. if (!this._client) this.createClient({ codec: 'h264', mode: 'live' })
  188. this._createTmpStream().then(() => {
  189. AgoraRTC.getDevices(devices => {
  190. this._localStream.close()
  191. resolve(devices)
  192. })
  193. })
  194. })
  195. }
  196. join(data) {
  197. this._joined = 'pending'
  198. return new Promise((resolve, reject) => {
  199. this._params = data
  200. // handle AgoraRTC client event
  201. this.handleEvents()
  202. // init client
  203. this._client.init(
  204. appID,
  205. () => {
  206. this._client.join(
  207. data.token ? data.token : null,
  208. data.channel,
  209. data.uid ? +data.uid : null,
  210. uid => {
  211. this._uid = uid
  212. this._joined = true
  213. data.uid = uid
  214. if (data.host) {
  215. this.createRTCStream(data)
  216. .then(() => {
  217. this.publish()
  218. resolve()
  219. })
  220. .catch(err => {
  221. reject(err)
  222. })
  223. } else {
  224. resolve()
  225. }
  226. },
  227. err => {
  228. this._joined = false
  229. reject(err)
  230. console.error('client join failed', err)
  231. }
  232. )
  233. },
  234. err => {
  235. this._joined = false
  236. reject(err)
  237. console.error(err)
  238. }
  239. )
  240. })
  241. }
  242. // 发布流
  243. publish() {
  244. this._client.publish(this._localStream, err => {
  245. console.error(err)
  246. })
  247. }
  248. // 取消发布
  249. unpublish() {
  250. if (!this._client) {
  251. return
  252. }
  253. this._client.unpublish(this._localStream, err => {
  254. console.error(err)
  255. })
  256. }
  257. // 关播 退出方法
  258. leave() {
  259. return new Promise(resolve => {
  260. // leave channel
  261. this._client.leave(
  262. () => {
  263. this._joined = false
  264. this.destroy()
  265. if (this._localStream && this._localStream.isPlaying()) {
  266. document
  267. .querySelector(`#player_${this._localStream.getId()}`)
  268. .remove()
  269. this._localStream.stop()
  270. }
  271. resolve()
  272. },
  273. err => {
  274. console.log('channel leave failed')
  275. console.error(err)
  276. }
  277. )
  278. })
  279. }
  280. }
  281. /** tools 后面会单独抽离工具方法 */
  282. // 判断不兼容的浏览器
  283. const showNotSupport = () => {
  284. var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream
  285. var isAndroid = navigator.userAgent.toLowerCase().indexOf('android') > -1
  286. var nativeBrowserName = '系统浏览器'
  287. if (isIOS) {
  288. nativeBrowserName = 'Safari'
  289. }
  290. if (isAndroid) {
  291. nativeBrowserName = 'Chrome'
  292. }
  293. console.log(nativeBrowserName)
  294. console.log(location.href)
  295. }
  296. // 判断是否在移动端环境
  297. const isMobile = () => {
  298. return /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)
  299. }

3 直播步骤(先申请好临时token, 房间号)

3.1 直播步骤( 以下方法通过RTS工具类封装过)

3.1.1 创建Client

  1. this.client = new RTCClient()
  2. this.client.createClient({ codec: 'h264', mode: 'live' })
参数名称 说明 示例
mode 使用场景 live
codec 编码格式 h264

3.2.2 加入房间

  1. this.client.join(this.options)
参数名称 说明 示例
options 详细内部参数如下
uid 用户uid 001
channel 房间名称 ealien
token 临时token与房间号绑定 asdasd
host 是否是主播 true

3.2.3 监听回调

  1. // 远端流加入触发
  2. this.client.on('stream-added', evt => {
  3. const remoteStream = evt.stream
  4. // 订阅远端流
  5. this.client.subscribe(remoteStream, err => {
  6. console.log(err)
  7. })
  8. })
  9. // 远端流订阅成功
  10. this.client.on('stream-subscribed', evt => {
  11. const remoteStream = evt.stream
  12. // 播放流
  13. remoteStream.play('live_remote-stream', err => {
  14. console.log(err)
  15. })
  16. })
  17. // 远端流移除
  18. this.client.on('stream-removed', e => {
  19. var stream = e.stream
  20. const id = stream.id
  21. // 删除视频容器
  22. id && document.querySelector(`#player-${id}`).remove()
  23. })
  24. // 远端用户加入触发
  25. this.client.on('peer-online', function(evt) {
  26. console.log('peer-online YOU 新用户加入', evt.uid)
  27. })
  28. // 远端流
  29. this.client.on('peer-leave', e => {
  30. console.log('通知 主播离开了' + e.uid)
  31. const uid = e.uid
  32. const reason = e.reason
  33. console.log('remote user left ', uid, 'reason: ', reason)
  34. })