Newer
Older
Nanping_sponge_SJJC / src / components / rongYun / index.vue
@liyingjing liyingjing on 25 Oct 19 KB 数据检测
<template>
  <div id="RongYun">
    <el-input v-model="value" type="text" placeholder="自定义用户名" />
    <div @click="Dengl">点击登录</div>

    <div class="rong-container">
      <div id="rongUser" class="rong-user">
        <span>用户 ID:</span>
        <span id="rongUserId"></span>
      </div>

      <div class="rong-im" id="rongIM">
        <p>请先进行 IM 连接</p>
        <!--appkey-->
        <div class="im-item">
          <label>App Key</label>
          <el-input
            type="text"
            id="appkey"
            v-model="AppKey"
            placeholder="请输入 App Key"
          />
        </div>
        <!--token-->
        <div class="im-item">
          <label>Token</label>
          <el-input
            type="text"
            id="token"
            v-model="tokens"
            placeholder="请输入 Token"
          />
        </div>
        <!--navi-->
        <div class="im-item">
          <label>Navi</label>
          <el-input type="text" id="navi" placeholder="请输入 Navi 地址" />
          <p>非必填;私有云环境必填</p>
        </div>
        <!--mediaServer-->
        <div class="im-item">
          <label>MediaServer</label>
          <el-input
            type="text"
            id="mediaServer"
            placeholder="请输入 MediaServer 地址"
          />
          <p>非必填;音视频服务地址</p>
        </div>
        <!--连接-->
        <div class="im-item">
          <button id="DengLuBTN" ref="DengLuBTN" @click="connectIM()">
            连接
          </button>
        </div>
      </div>

      <div class="rong-call" id="rongCall" style="display: none">
        <!--呼叫选项-->
        <div id="callParam" class="call-param">
          <!--选择通话类型-->
          <div class="param-item">
            <label>通话类型</label>
            <select name="" id="callType" @change="callTypeChange()">
              <option value="1">单聊</option>
              <option value="3">群聊</option>
            </select>
            <p>必选</p>
          </div>
          <!--mediaType-->
          <div class="param-item">
            <label>媒体类型</label>
            <select name="" id="callMediaType" @change="callMediaTypeChange()">
              <option value="1">音频</option>
              <option value="2">音视频</option>
            </select>
            <p>必选</p>
          </div>
          <!--targetId-->
          <div id="paramPrivate" class="param-item">
            <label>对方 ID</label>
            <el-input id="targetId" type="text" placeholder="对方 userId" />
            <p>
              必填;对方的 userId,可通过<a
                target="_blank"
                href="https://developer.rongcloud.cn/"
                >[开发者后台] -> [服务管理] -> [API 调用] -> [用户服务] -> [获取
                token]</a
              >获取,且登录成功
            </p>
          </div>
          <!--targetId-->
          <div id="paramGroupId" class="param-item" style="display: none">
            <label>群组 ID</label>
            <el-input id="groupId" type="text" placeholder="群组 ID" />
            <p>
              必填;可通过
              <a target="_blank" href="https://developer.rongcloud.cn/"
                >[开发者后台] -> [服务管理] -> [API 调用] -> [群组服务] ->
                [加入群组]</a
              >
              加入群组后获取
            </p>
          </div>
          <!--userIds 只有群显示-->
          <div id="paramInvitedIds" class="param-item" style="display: none">
            <label>被邀请者 ID</label>
            <el-input
              id="userIds"
              type="text"
              placeholder="多个 userId 用英文半角逗号分开"
            />
            <p>
              必填;需加入群后,方可收到邀请。多个 userId 用英文半角逗号分开
            </p>
          </div>
        </div>
        <!--通话操作按钮-->
        <div class="opt-btn">
          <button id="callBtn" class="btn-call" @click="call()">呼叫</button>
          <button id="acceptBtn" class="btn-accept" @click="accept()">
            接听
          </button>
          <button id="hungupBtn" class="btn-hungup" @click="hungup()">
            挂断
          </button>
        </div>
        <!--通话视图展示-->
        <div id="videoView" class="video-view"></div>
      </div>
    </div>
    <!-- 显示一些操作的信息 -->
    <div class="toast-wrap">
      <span class="toast-msg"></span>
    </div>
  </div>
</template>
<script setup name="RongYun">
import { ref, reactive, toRefs, onMounted, nextTick } from "vue";
import { userGetToken } from "@/api/RongYun";
import configs from "@/utils/config.js";

// 插件内置:------------------------------------------------------------------E

const AllData = reactive({
  AppKey: configs.AppKey,
  tokens: "",
  DengLuBTN: ref(null),
  userId: "newfiber",
});
const { AppKey, tokens, DengLuBTN, userId } = toRefs(AllData);
// 插件内置:------------------------------------------------------------------A
/**
 * utils
 */
const RCDom = {
  get: (id) => {
    return document.getElementById(id);
  },
  show: (id) => {
    var rongIMDom = document.getElementById(id);
    rongIMDom.setAttribute("style", "display:inline-block;");
  },
  showBlock: (id) => {
    var rongIMDom = document.getElementById(id);
    rongIMDom.setAttribute("style", "display:block;");
  },
  hide: (id) => {
    var rongIMDom = document.getElementById(id);
    rongIMDom.setAttribute("style", "display:none;");
  },
};

const RCToast = (msg) => {
  setTimeout(function () {
    document
      .getElementsByClassName("toast-wrap")[0]
      .getElementsByClassName("toast-msg")[0].innerHTML = msg;
    var toastTag = document.getElementsByClassName("toast-wrap")[0];
    toastTag.className = toastTag.className.replace("toastAnimate", "");
    setTimeout(function () {
      toastTag.className = toastTag.className + " toastAnimate";
    }, 10);
  }, 10);
};

const RCCallView = {
  connectedIM: () => {
    RCDom.show("rongUser");
  },
  readyToCall: () => {
    RCDom.hide("rongIM");
    RCDom.show("rongCall");
  },
  outgoing: () => {
    RCDom.hide("callParam");
    RCDom.hide("callBtn");
    RCDom.show("hungupBtn");
  },
  incomming: () => {
    RCDom.hide("callParam");
    RCDom.hide("callBtn");
    RCDom.show("acceptBtn");
    RCDom.show("hungupBtn");
  },
  inTheCall: () => {
    RCDom.hide("acceptBtn");
    RCDom.hide("callParam");
    RCDom.show("hungupBtn");
  },
  end: () => {
    RCDom.show("callParam");
    RCDom.show("callBtn");
    RCDom.hide("acceptBtn");
    RCDom.hide("hungupBtn");
  },
};

/**
 * im
 */
/**
 * 初始化、链接 IM 相关逻辑
 */

// IM 实例
let imClient;

const connectIM = () => {
  const appkey = RCDom.get("appkey").value;
  const token = RCDom.get("token").value;
  const navi = RCDom.get("navi").value;

  if (!appkey) {
    RCToast("请输入 App Key");
    return;
  }
  if (!token) {
    RCToast("请输入 Token");
    return;
  }

  // IM 客户端初始化
  imClient = RongIMLib.init({
    appkey,
    navigators: navi ? [navi] : undefined,
    logLevel: 1,
  });

  // 初始化 RTC CallLib
  initRTC();
  initCall();

  imClient.watch({
    // 监听 IM 连接状态变化
    status(evt) {
      console.log("connection status change:", evt.status);
    },
  });

  RCToast("正在链接 IM ... ☕️");
  imClient
    .connect({ token })
    .then((user) => {
      RCCallView.connectedIM();
      RCCallView.readyToCall();
      RCDom.get("rongUserId").innerText = user.id;
      RCToast(`用户 ${user.id} IM 链接成功 ✌🏻`);
    })
    .catch((error) => {
      console.log(error);
      RCToast("IM 链接失败,请检查网络后再试");
    });
};

/**
 *call
 */
/**
 * call 主要相关逻辑
 */
const { ConversationType } = RongIMLib;
const { RCCallMediaType, RCCallErrorCode } = RCCall;
// CallSession 实例
let callSession;
// Call 呼叫类型
let callType = ConversationType.PRIVATE;
// Call 媒体类型
let mediaType = RCCallMediaType.AUDIO;

// RTC 实例
let rtcClient;
// CallLib 实例
let callClient;

/**
 * RTC 初始化
 * 在 IM 初始化后进行初始化 (具体位置:im.js)
 */
const initRTC = () => {
  const mediaServer = RCDom.get("mediaServer").value;
  rtcClient = imClient.install(window.RCRTC.installer, {
    mediaServer: mediaServer || undefined,
    timeout: 30 * 1000,
    logLevel: window.RCEngine.LogLevel.DEBUG,
  });
};

/**
 * CallLib 初始化
 * 在 IM 初始化后进行初始化 (具体位置:im.js)
 */
const initCall = () => {
  callClient = imClient.install(window.RCCall.installer, {
    rtcClient: rtcClient,
    onSession: (session) => {
      callSession = session;
      mediaType = session.getMediaType();
      registerCallSessionEvent(callSession);
      RCToast(`收到 ${session.getCallerId()} 的通话邀请`);
      RCCallView.incomming();
    },
    onSessionClose: (session, summary) => {
      RCToast("通话已结束");
      RCCallView.end();
      removeVideoEl();
    },
  });
};

/**
 * 通话类型监听
 */
const callTypeChange = () => {
  const callTypeDom = RCDom.get("callType");
  callType = Number(callTypeDom.value);
  if (callType === ConversationType.GROUP) {
    RCDom.showBlock("paramGroupId");
    RCDom.showBlock("paramInvitedIds");
    RCDom.hide("paramPrivate");
  } else {
    RCDom.hide("paramGroupId");
    RCDom.hide("paramInvitedIds");
    RCDom.showBlock("paramPrivate");
  }
};

/**
 * 媒体类型监听
 */
const callMediaTypeChange = () => {
  const mediaTypeDom = RCDom.get("callMediaType");
  mediaType = Number(mediaTypeDom.value);
};

/**
 * CallSession 事件
 */
const getCallSessionEvent = () => {
  return {
    onRinging: (sender) => {
      // 当远端用户已开始响铃, 该函数有 2 个参数: sender 是发送者,session 是通话实例。这时用户可以在业务层做响铃的 UI 展示。
      RCToast(`收到 ${sender.userId} 振铃`);
      message.info(`收到 ${sender.userId} 振铃`);
    },
    onAccept: (sender) => {
      // 当远端用户已同意接听, 该函数有 2 个参数: sender 是发送者,session 是通话实例。这时用户可以把 UI 的‘响铃’变成‘通话中’。
      RCToast(`${sender.userId} 已接听`);
    },
    onHungup: (sender) => {
      // 当有远端用户挂断, 该函数有 3 个参数: sender 是发送者,reason 是挂断原因,session 是通话实例。这时用户可以在 UI 层提示‘xxx已挂断’。
      RCToast(`${sender.userId} 已挂断`);
      // 群组中移除相应节点
      const videoViewDom = RCDom.get("videoView");
      const videoDom = RCDom.get(`video-${sender.userId}`);
      videoDom && videoViewDom.removeChild(videoDom);
    },
    onTrackReady: (track) => {
      // 当本端资源或远端资源已获取, 该函数有 2 个参数:track 是本端资源或远端资源, session 是通话实例。这时用户可以用拿到的 track 播放音频、视频。
      appendVideoEl(track);
      if (!track.isLocalTrack()) {
        RCToast("通话已建立");
        RCCallView.inTheCall();
      }
    },
    onMemberModify: (sender, invitedUsers) => {
      // 群组通话中有其他人被邀请加入, 该函数有 3 个参数: sender 是发送者,invitedUsers 是被邀请的用户列表, session 是通话实例。这时用户可以在 UI 层提示‘xxx加入通话’。
    },
    onMediaModify: (sender) => {
      // 通话类型改变时触发, 该函数有3个参数: sender 是发送者,mediaType 通话类型, session 是通话实例。这时用户可以在 UI 层提示‘已降级成音频通话’。
    },
    onAudioMuteChange: (muteUser) => {
      // 对方静音后触发, 该函数有2个参数: muteUser 是已静音的用户, session 是通话实例。这时用户可以在 UI 层提示‘xxx已静音’。
    },
    onVideoMuteChange: (muteUser) => {
      // 对方禁用视频后触发, 该函数有2个参数: muteUser 是已禁用视频的用户, session 是通话实例。这时用户可以在 UI 层提示‘xxx已禁用视频’。
    },
  };
};

/**
 * callSession 事件注册
 */
const registerCallSessionEvent = (session) => {
  const events = getCallSessionEvent();
  session.registerSessionListener(events);
};

/**
 * callSession 呼叫
 */
const call = () => {
  const events = getCallSessionEvent();
  const isPrivateCall = callType === ConversationType.PRIVATE;
  const params = {
    targetId: RCDom.get(`${isPrivateCall ? "targetId" : "groupId"}`).value,
    mediaType: mediaType,
    listener: events,
  };
  if (isPrivateCall) {
    if (!RCDom.get("targetId").value) {
      RCToast("请输入对方 ID");
      return;
    }
    privateCall(params);
  } else {
    if (!RCDom.get("groupId").value) {
      RCToast("请输入群组 ID");
      return;
    }
    if (!RCDom.get("userIds").value) {
      RCToast("请输入被邀请者 ID");
      return;
    }
    groupCall(params);
  }
};

/**
 * 单呼
 */
const privateCall = (params) => {
  callClient.call(params).then(({ code, session }) => {
    if (code === RCCallErrorCode.SUCCESS) {
      registerCallSessionEvent(session);
      callSession = session;
      RCCallView.outgoing();
    } else {
      RCToast(`呼叫失败,错误原因:${code}`);
    }
  });
};

/**
 * 群呼
 */
const groupCall = (params) => {
  // debugger;
  params.userIds = (RCDom.get("userIds").value || []).split(",");
  callClient.callInGroup(params).then(({ code, session }) => {
    if (code === RCCallErrorCode.SUCCESS) {
      registerCallSessionEvent(session);
      callSession = session;
      RCCallView.outgoing();
    } else {
      const reason =
        code === RCCallErrorCode.NOT_IN_GROUP ? "当前用户未加入群组" : code;
      RCToast(`呼叫失败,错误原因:${reason}`);
      removeVideoEl();
    }
  });
};

/**
 * 接听当前 callSession
 */
const accept = () => {
  callSession.accept().then(({ code }) => {
    if (code === RCCallErrorCode.SUCCESS) {
      RCToast("接听成功");
    } else {
      RCToast(`接听失败,错误原因:${code}`);
    }
  });
};

/**
 * 挂断当前 callSession
 */
const hungup = () => {
  callSession.hungup().then(({ code }) => {
    if (code === RCCallErrorCode.SUCCESS) {
      RCToast("挂断成功");
    } else {
      RCToast(`挂断失败,错误原因:${code}`);
    }
  });
};

/**
 * video 视图渲染
 */
const appendVideoEl = (track) => {
  // debugger
  const container = RCDom.get("videoView");
  if (track.isAudioTrack()) {
    const uid = track.getUserId();
    const node = document.createElement("div");
    node.setAttribute("id", `video-${uid}`);
    const videoTpl = `<span class="video-user-id">ID: ${uid}</span>
        <span class="video-media-type">${mediaType === 1 ? "音频" : ""}</span>
        <video id="${uid}"></video>`;
    node.innerHTML = videoTpl;
    node.classList = "video-item";
    container.appendChild(node);
    track.play();
  } else {
    const videoEl = RCDom.get(track.getUserId());
    track.play(videoEl);
  }
};

/**
 * 通话结束后,清除所有 video 标签
 */
const removeVideoEl = () => {
  RCDom.get("videoView").innerHTML = "";
};

// 默认登录,获取当前电脑用户PCUser的通话token
const Dengl = async () => {
  let res = await userGetToken(`?userId=${userId.value}&name=管理员`);
  if (res.code == 200) {
    //   融云登录成功
    tokens.value = res.token;
    nextTick(() => {
      connectIM();
    });
  }
};
onMounted(() => {
  //Dengl();
});
</script>
<style lang="scss" scoped>
#RongYun {
  width: 100%;
  height: 100%;
  .rong-container {
    background: #222831;
    // min-height: 100%;
    // min-width: 1400px;
    color: #fff;
    font-size: 18px;
    text-align: center;
  }
  .rong-container .rong-title {
    margin: 0;
    padding: 25px 0 10px;
    text-align: center;
    color: #fff;
    font-size: 30px;
  }
  .rong-container .rong-user {
    display: none;
    position: absolute;
    top: 70px;
    left: 100px;
  }
  .rong-container button {
    width: 150px;
    height: 30px;
    border-radius: 2px;
    background: #2da2ea;
    border: none;
    color: #fff;
    box-shadow: 1px 1px 6px #00000069;
    cursor: pointer;
  }
  .rong-container button:active {
    width: 150px;
    height: 30px;
    border-radius: 2px;
    background: #0f93e4;
    border: none;
    color: #fff;
    box-shadow: 1px 1px 6px #00000069;
    cursor: pointer;
    /* opacity: .8; */
  }
  .rong-container .rong-im {
    background: #eeeeee1c;
    width: 850px;
    border-radius: 2px;
    display: inline-block;
  }
  .rong-container .rong-im .im-item {
    text-align: center;
    margin-bottom: 10px;
    height: 50px;
    position: relative;
  }
  .rong-container .rong-im .im-tips {
    width: 100px;
  }
  .rong-container .rong-im .im-item label {
    display: inline-block;
    width: 150px;
    text-align: right;
  }
  .rong-container .rong-im .im-item input {
    border: 1px solid #ccc;
    border-radius: 2px;
    height: 25px;
    width: 600px;
  }
  .rong-container .rong-im .im-item p {
    margin: 0px;
    display: inline-block;
    position: absolute;
    left: 200px;
    top: 30px;
    color: #fff;
    font-size: 13px;
  }
  .rong-container .rong-call {
    text-align: center;
    margin-bottom: 10px;
  }
  .rong-container .rong-call .call-param {
    padding: 10px;
    background: #eeeeee1c;
    width: 650px;
    border-radius: 2px;
    display: inline-block;
  }
  .rong-container .rong-call select {
    background: #4285f4;
    width: 450px;
    height: 30px;
    border-radius: 2px;
    border: none;
    color: #fff;
    text-align: center;
  }

  .rong-container .rong-call label {
    display: inline-block;
    width: 150px;
    text-align: right;
  }
  .rong-container .rong-call input {
    border: 1px solid #ccc;
    border-radius: 2px;
    height: 25px;
    width: 450px;
  }

  .rong-container .rong-call .call-item,
  .rong-container .rong-call .call-param,
  .rong-container .rong-call .opt-btn {
    margin-bottom: 10px;
  }

  .rong-container .rong-call .call-param .param-item {
    margin-bottom: 15px;
  }

  .rong-container .rong-call .call-param .param-item {
    height: 50px;
    position: relative;
  }
  .rong-container .rong-call .call-param .param-item p {
    margin: 0px;
    display: inline-block;
    position: absolute;
    left: 180px;
    top: 30px;
    color: #fff;
    font-size: 13px;
    text-align: left;
  }
  .rong-container .rong-call .opt-btn .btn-accept,
  .rong-container .rong-call .opt-btn .btn-hungup {
    display: none;
  }

  .rong-container .rong-call .video-view .video-item {
    width: 320px;
    height: 240px;
    background: #1a7cb9;
    position: relative;
    border-radius: 2px;
    display: inline-block;
    margin: 8px;
  }
  .rong-container .rong-call .video-view .video-item .video-user-id {
    background: #a8d8ea9c;
    position: absolute;
    left: 2px;
    display: inline-block;
    padding: 0 5px;
    border-radius: 2px;
  }
  .rong-container .rong-call .video-view .video-item .video-media-type {
    position: absolute;
    top: 41%;
    right: 42%;
  }
  .rong-container .rong-call .video-view video {
    width: 320px;
    height: 240px;
    border-radius: 2px;
  }

  /* toast */
  .toast-wrap {
    display: inline-block;
    opacity: 0;
    position: fixed;
    top: 5%;
    color: #fff;
    width: 100%;
    text-align: center;
  }
  .toast-msg {
    background-color: rgba(89, 148, 236, 0.918);
    border-radius: 2px;
    padding: 10px 15px;
  }
  .toastAnimate {
    animation: toastKF 2s;
  }
  @keyframes toastKF {
    0% {
      opacity: 0;
    }
    25% {
      opacity: 1;
      z-index: 9999;
    }
    50% {
      opacity: 1;
      z-index: 9999;
    }
    75% {
      opacity: 1;
      z-index: 9999;
    }
    100% {
      opacity: 0;
      z-index: 0;
    }
  }
}
</style>