zoukankan      html  css  js  c++  java
  • 基于七牛RTN实现多人在线会议或课堂(二)

    一、采集和发布本地的track

      采集本地音视频轨这个操作涉及到 2 个模块 —— deviceManager 和 Track

      deviceManager:SDK的媒体设备管理模块,用于监听媒体设备变化及发起采集操作。

      Track:采集方法的返回。Track模式下,所有可在页面上播放的媒体元素,都称为Track对象。

    1、发起本地采集的方法

    // 采集并发布本地的音视频轨
    let screenLocalTracks = await QNRTC.deviceManager.getLocalTracks({
        screen: {enabled: true, tag: "screen"}   // 采集屏幕共享
    });
    
    // 采集并发布本地的音频轨
    let audioLocalTracks = await QNRTC.deviceManager.getLocalTracks({
        audio: {enabled: true, tag: "audio"},    // 采集音频
    });
    
    // 采集并发布本地的视频轨
    let videoLocalTracks = await QNRTC.deviceManager.getLocalTracks({
        video: {enabled: true, tag: "video"}     // 采集视频
    });

      getLocalTracks 可以重复调用,即时指定的配置项完全相同,每次调用返回的Track也是各自独立的。

    2、播放本地音视频轨

      通过如下操作,即可在页面指定元素内播放音频、视频。

    // 获取页面上一个元素作为播放画面的父元素
    let mainElement = document.getElementById("maintracks");
    let localElement = document.getElementById("localtracks");
    
    
    // 将 Track 列表发布到房间中
    await this.myRoom.publish(screenLocalTracks);
    // 遍历本地采集的屏幕对象
    for (const screenTrack of screenLocalTracks) {
      this.localTracks.push(screenTrack);
      screenTrack.play(mainElement, true);
    }
    self.localScreenStatus = true;
    console.log("发布后我的本地Track", key, this.localTracks);
    
    
    // 将 Track 列表发布到房间中
    await this.myRoom.publish(audioLocalTracks);
    // 遍历本地采集的Track对象,不播放自己的音频
    for (const audioTrack of audioLocalTracks) {
      this.localTracks.push(audioTrack);
    }
    self.localAudioStatus = true;
    console.log("发布后我的本地Track", key, this.localTracks);
    
    
    // 将 Track 列表发布到房间中
    await this.myRoom.publish(videoLocalTracks);
    // 遍历本地采集的Track对象并播放
    for (const videoTrack of videoLocalTracks) {
      this.localTracks.push(videoTrack);
      videoTrack.play(localElement, true);
    }
    console.log("发布后我的本地Track", key, this.localTracks);

      SDK 会自动在 domElement 下创建 <audio> 或者 <video> 元素来播放媒体(使用 audio 还是 video 取决于 Track 本身的 kind)。

    3、浏览器自动播放策略及处理

      在没有与用户任何交互的情况下调用play()方法会导致音频无法播放。

      在实际应用场景中,经常需要实现加入房间之后进行自动的发布和订阅。其中自动订阅往往伴随着自动播放。

      浏览器再没有交互操作之前不允许有声音的媒体自动播放,自动播放策略如下:

    • 始终允许静音(muted)的视频自动播放
    • 以下情况允许带声音的自动播放
      • 用户已经在访问的域名下有交互操作
      • 顶级页面可以把autoplay权限委托给iframe,从而允许自动播放声音

      各个浏览器实现:

    二、发布/取消本地的Track

      可以看到前面在播放Track前都将这些Track发布到房间中供其他人订阅。

    1、发布本地Track

    // 采集并发布本地的音视频轨
    let screenLocalTracks = await QNRTC.deviceManager.getLocalTracks({
        screen: {enabled: true, tag: "screen"}   // 采集屏幕共享
    });
    // 将 Track 列表发布到房间中
    await this.myRoom.publish(screenLocalTracks);

      当一个Track对象经过发布操作后,有2个值的变化需要注意:

    • track.userId:将会标记为加入这个房间用户的 userId
    • track.info.trackId:因为Track发布到房间,会被分配一个这个房间相对其他Track唯一的trackId
    • trackId是房间内所有 Track 的唯一标识,需要指定 Track 的操作都会要求提供 trackId 参数

    2、取消发布音视频轨

      由于我需要让用户可以轮流投屏桌面、发布或取消发布自己的声音。因此不使用 Track 的 mute 状态,统一使用取消发布Track。

    <script>
      import * as QNRTC from "pili-rtc-web";
    
      export default {
        name: 'VideoConference',
        methods: {
          // 取消发布
          async unpublish(key) {
            var self = this;
            var tag = '';
            if (key === 'speaker') {
              tag = "screen";
              self.localScreenStatus = false;
            } else if (key === 'voice') {
              self.localAudioStatus = false;
              tag = "audio";
            } else {
              tag = "video";
            }
    
            for (let i = self.localTracks.length - 1; i >= 0; i--) {
              if (self.localTracks[i].info.tag === tag) {
                // 取消发布
                await self.myRoom.unpublish([self.localTracks[i].info.trackId]);
    
                // 释放本地资源
                self.localTracks[i].release();
                self.localTracks.splice(i, 1);
    
                console.log("取消发布后我的本地Track", key, this.localTracks);
              }
            }
          },
        },
      }
    </script>

      遍历所有的本地track,查看info.tag信息。判断track类型:screen、voice、audio。

      如果key='speaker'则停止发布screen媒体流。如果是video则停止发布video媒体流。如果是audio则停止发布audio媒体流。

    3、销毁本地Track

      由于在七牛的SDK里,退出情况房间或者取消发布并不会销毁本地Track,这些音视频轨也不会被释放。

      如果音视频轨没有及时释放,它们会一直占用摄像头/麦克风等媒体设备。需要用release()方法逐个销毁释放。

      前面取消音视频轨的代码中,就在取消的同时,销毁释放track:

    for (let i = self.localTracks.length - 1; i >= 0; i--) {
      if (self.localTracks[i].info.tag === tag) {
        // 取消发布
        await self.myRoom.unpublish([self.localTracks[i].info.trackId]);
    
        // 释放本地资源
        self.localTracks[i].release();
        self.localTracks.splice(i, 1);
    
        console.log("取消发布后我的本地Track", key, this.localTracks);
      }
    }

      退出房间时也会用到release销毁音视频轨:

    // 退出房间
    exitMeeting(id) {
      var self = this;
      if (id === 'localtracks') {
        // 销毁释放本地音视频轨
        if (self.localTracks) {
          for (let localTrack of self.localTracks) {
            localTrack.release();
          }
        }
        // 离开房间
        self.myRoom.leaveRoom();
        // 跳转到首页
        window.location.href = '/';
      } else {
        console.log(id);
        // self.kickOut(id);
        self.myRoom.sendCustomMessage('quitRoom', [id]);
      }
    },

    三、订阅远端的音视频轨

      这一部分官方文档非常简陋。但却是业务实现的核心。

      在前面代码中,创建房间时,首先执行publish函数自动播放自己本地视频流。随后则是启动自动订阅(autoSubscribe),随时订阅到其他加入房间的track。

    // 创建房间
    async joinRoom(token) {
      // 初始化一个房间Session对象,这里使用Track模式
      const myRoom = new QNRTC.TrackModeSession();
      this.myRoom = myRoom;
      // 使用 RoomToken加入房间
      await myRoom.joinRoomWithToken(token);
      // 自动加载视频
      this.publish();
      this.autoSubscribe(myRoom);
    },

    1、由TrackInfo列表获取用户列表

      由myRoom.trackInfoList可以获取到当前房间中的 TrackInfo 列表。

    // 获取订阅列表
    autoSubscribe(myRoom) {
      let self = this;
      // 加入房间成功后,就可以通过访问myRTC.trackInfoList获取房间当前其他人的TrackInfo
      self.trackInfoList = myRoom.trackInfoList;
      console.log("房间当前音视频轨对象列表!", self.trackInfoList);
    
      self.userId = myRoom.userId;    // 自己的userId
      self.remoteUserList = [];
      for (var index in self.myRoom.users) {
        if (self.myRoom.users[index].userId !== self.userId) {
          self.remoteUserList.push({
            userId: self.myRoom.users[index].userId,
            isLive: true,
            screenStatus: false,
            audioStatus: false,
            drawStatus: false,
            user: self.myRoom.users[index]
          });
        }
      }
      for (let i = self.remoteUserList.length; i < 20; i++) {
        self.remoteUserList.push({
          userId: '',
          isLive: false,
          screenStatus: false,
          audioStatus: false,
          drawStatus: false,
          user: null
        });
      }
      console.log("房间当前用户", self.remoteUserList);

      myRoom.users:获取的user列表在页面遍历时,会发现存在重复,并包含自己的user信息。因此需要重新构造一个列表。

      但是用v-for在页面遍历时,会发现另一个问题:每次track发生变化,列表都会发生变化,随后均会导致v-for遍历生成的dom销毁重建。一般情况下不影响,但这里dom中包含视频和音频标签,会导致音频或视频丢失。因此用如上方式创建固定长度列表,仅替换元素值,不增减值。

    2、订阅远端发布的音视频轨

      如上所示autoSubscribe()方法中,获取到房间中 TrackInfo 列表和用户列表后。订阅房间已经有的所有track。

    if (self.trackInfoList.length > 0) {
      // 取出每个 TrackInfo 的 trackId 当作参数发起订阅
      self.subscribe(self.trackInfoList)
        .then(() => console.log("订阅成功!"))
        .catch(e => console.error("订阅失败", e));
    }

      官方文档中发起订阅核心示例方法:

    // 过滤 tag 为 screen_track 的 TrackInfo
    const filterTrackInfoList = trackInfoList.filter(info => info.tag !== "screen_track");
    
    // 取出每个 TrackInfo 的 trackId 当作参数发起订阅
    const tracks = await myRoom.subscribe(filterTrackInfoList.map(info => info.trackId));

      文档中返回的 tracks 就是 Track 对象列表,对应相应的 TrackInfo,可以访问 Track 的 info 来查看它的 TrackInfo。

      订阅远端发布的track并用play方法在页面播放:

    // 订阅远端发布的音视频轨
    // trackInfoList 是一个 trackInfo 的列表,订阅支持多个 track 同时订阅
    async subscribe(trackInfoList) {
      // 通过传入 trackId 调用订阅方法发起订阅,成功会返回相应的Track对象,也就是远端的 Track列表
      let remoteTracks = await this.myRoom.subscribe(trackInfoList.map(info => info.trackId));
      console.log('远端Track列表', remoteTracks);
    
      // 遍历返回远端的Track,调用play方法完成页面播放
      for (const remoteTrack of remoteTracks) {
        // 选择页面上的一个元素作为元素,播放远端的音视频轨
        let mainElement = document.getElementById("maintracks");
        let remoteElement = document.getElementById(remoteTrack.userId);
        let remoteVolElement = document.getElementById(remoteTrack.userId + '_vol');
        // 如果这是麦克风采集的音频Track,则不播放它
        if (remoteTrack.info.tag === "screen") {
          remoteTrack.play(mainElement, true);
        } else if (remoteTrack.info.tag === "video") {
          remoteTrack.play(remoteElement, true);
        } else if (remoteTrack.info.tag === "audio") {
          remoteTrack.play(remoteVolElement, false)
        }
      }
      console.log('调阅后的远端Track列表', remoteTracks);
    },

    3、取消订阅

      当成功订阅获取 Track 之后,就可以选择这些 Track 来取消订阅了。

      取消订阅操作完成后,SDK会自动释放相应的媒体对象。

    // 取消订阅
    async unsubscribe(trackInfoList) {
      var self = this;
      // 从刚刚订阅返回的 tracks 中找到视频轨
      let remoteTracks = await self.myRoom.unsubscribe(trackInfoList.map(info => info.trackId));
    },

      以清除已存在的scream媒体流,自己投屏为例:

    if (key === 'speaker') {
      // 清除已存在的screen媒体流
      self.trackInfoList = self.myRoom.trackInfoList;
      for (let item of self.trackInfoList) {
        if (item.tag === "screen") {
          // 自己停止订阅
          self.unsubscribe([item]);
          // 通知对方停止投屏
          self.myRoom.sendCustomMessage('stopScreen', [item.userId]);
        }
      }

    四、事件监听处理

      TrackModeSession 是 Track 模式下的房间管理模块,所有和房间有关的操作都通过该模块实现。

      官方API文档地址:https://doc.qnsdk.com/rtn/web/docs/api_track_mode_session

      在加入房间时,已经初始化了一个房间TrackModeSession对象:

    import * as QNRTC from "pili-rtc-web";
    
    const myRoom = new TrackModeSession();

    1、监听新track发布事件

      前面在进入房间时订阅了所有已经存在的track,但是后面再进入房间的客户或者新产生的track,需要实时监听并订阅。

      track-add:可以监听到房间内其他用户发布的Track。

            事件参数:tracks——Array<TrackInfo> 新发布Track的TrackInfo。

    // 添加事件监听。当房间出现新的 Track 时触发,参数是 trackInfo 列表
    myRoom.on("track-add", trackInfoList => {
      // 房间里有新的track发布
      console.log("Track新增!", trackInfoList);
    
      self.subscribe(trackInfoList)
        .then(() => console.log("订阅成功!"))
        .catch(e => console.log("订阅失败!", e))
    });

      在监控到新Track,执行订阅前,可以触发更新房间user列表及执行其他操作,这里省略。

    2、监听track取消发布事件

      track-remove:监听到其他用户取消发布了Track。可以和前面的unpublish产生配合。

             事件参数:tracks——Array<TrackInfo>取消发布Track的TrackInfo。

    <script>
      export default {
        name: 'VideoConference',
    
        methods: {
          // 获取订阅列表
          autoSubscribe(myRoom) {
    
            myRoom.on("track-remove", trackInfoList => {
              // 房间里有 Track 取消发布
              console.log("Track移除", trackInfoList, self.myRoom.users);
    
              self.unsubscribe(trackInfoList)
                .then(() => console.log("取消订阅成功!"))
                .catch(e => console.log("取消订阅失败!", e))
            });

      在监控到Track取消发布时,执行取消订阅前,同样可以触发更新房间user列表。

    3、消息发送和接收

      虽然RTN的SDK中没有介绍,但是它的核心功能是利用webSocket实现。查看源码可以找到消息发送和接收的方法。

    (1)发送消息

      这里以发送是否允许投屏为例展示:

    // 控制其他用户投屏权限
    for (let remoteUser of self.remoteUserList) {
      if (remoteUser.userId === id) {
        if (!remoteUser.screenStatus) {
          // 开启禁止投屏
          self.myRoom.sendCustomMessage('enableScreen', [id]);
          remoteUser.screenStatus = true;
        } else {
          // 取消禁止投屏
          self.myRoom.sendCustomMessage('disableScreen', [id]);
          remoteUser.screenStatus = false;
        }
        return;
      }
    }

    (2)接收消息

      这里以接收投屏消息为例:

    myRoom.on("messages-received", trackInfoList => {
      console.log("send-message", trackInfoList);
      if (trackInfoList[0].data === 'enableScreen') {
        // 允许投屏
        self.localScreenDisable = false;
        self.setSpeaker("localtracks");
      } else if (trackInfoList[0].data === 'disableScreen') {
        // 禁止投屏
        self.localScreenDisable = true;
        self.setSpeaker("localtracks");
      }
  • 相关阅读:
    Linux系统root密码修改
    网络通信
    运维平台cmdb开发-day1
    questions information
    Django Rest Framework
    Django-CBV和跨域请求伪造
    Flask学习
    会议室预定终章
    python的可变数据类型和不可变类型
    模拟admin组件自己开发stark组件之搜索和批量操作
  • 原文地址:https://www.cnblogs.com/xiugeng/p/12767214.html
Copyright © 2011-2022 走看看