123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- import { reactive, ref, Ref } from 'vue'
- import * as RongIMLib from '@rongcloud/imlib-next'
- import * as RTC from '@rongcloud/plugin-rtc'
- import request from '/src/helpers/request'
- import { state } from '/src/state'
- import event, { LIVE_EVENT_MESSAGE } from './event'
- import dayjs from 'dayjs'
- // import { SeatsCtrl } from './message-type'
- type imConnectStatus = 'connecting' | 'connected' | 'disconnect'
- type VideoStatus = 'init' | 'stream' | 'liveing' | 'stopped' | 'error' | 'loading'
- export type TrackType = 'microphone' | 'camera' | 'screen'
- type ActiveTracks = {
- [key in TrackType]: RTC.RCLocalTrack | null
- }
- type DeviceStatus = {
- [key in TrackType]: 'init' | 'granted' | 'denied' | 'closed' | 'none'
- }
- export const START_LIVE_TIME = 'start-live-time'
- export const START_LIVE_STATUS = 'start-live-status'
- export const VIDEO_DEVICE_ID = 'video-deviceId'
- export const AUDIO_DEVICE_ID = 'audio-deviceId'
- export const AUDIO_DEVICE_VOLUME = 'audio-device-volume'
- const runtime = reactive({
- /** 房间id */
- roomUid: 'LIVE-2112263-12345',
- /** IM连接状态 */
- imConnectStatus: 'connecting' as imConnectStatus,
- // 屏幕分享状态
- screenShareStatus: false,
- // 视频节点
- videoRef: ref<HTMLVideoElement | null>(null),
- // RTC实例
- rtcClient: null as RTC.RCRTCClient | null,
- /** 加入房间实例 */
- joinedRoom: null as RTC.RCLivingRoom | null,
- // Tracks
- mediaStreamTrack: [] as MediaStreamTrack[],
- // 媒体流
- mediaStreams: null as MediaStream | null,
- // 视频状态
- videoStatus: 'init' as VideoStatus,
- // 麦克风设备列表
- microphones: [] as MediaDeviceInfo[],
- // 摄像头设备列表
- cameras: [] as MediaDeviceInfo[],
- // 摄像头设备
- selectedCamera: null as MediaDeviceInfo | null,
- // 麦克风设备
- selectedMicrophone: null as MediaDeviceInfo | null,
- // 点赞数量
- likeCount: 0,
- // 上一次点赞数量
- lastLikeCount: 0,
- /** 当前活跃的数据流 */
- activeTracks: {} as ActiveTracks,
- /** 是否关闭连麦 */
- allowSeatsCtrl: true,
- /** 是否关闭发言 */
- allowChatCtrl: true,
- /** 当前设备获取状态 */
- deviceStatus: {
- microphone: 'init',
- camera: 'init',
- screen: 'init'
- } as DeviceStatus
- })
- export default runtime
- const RONG_IM_TOKEN = 'c9kqb3rdc451j'
- RongIMLib.init({
- appkey: RONG_IM_TOKEN,
- })
- // 注册自定义消息类型
- // 控制是否允许连麦
- const MessageSeatsCtrl = RongIMLib.registerMessageType('RC:Chatroom:SeatsCtrl', true, true)
- // 控制是否允许发言
- const MessageChatBan = RongIMLib.registerMessageType('RC:Chatroom:ChatBan', true, true)
- // 连麦消息
- const MessageSeatApply = RongIMLib.registerMessageType('RC:Chatroom:SeatApply', true, true)
- // 响应连麦消息
- const MessageSeatResponse = RongIMLib.registerMessageType('RC:Chatroom:SeatResponse', true, true)
- type MessageProps = {
- messageType: 'RC:Chatroom:Welcome' | 'RC:TxtMsg' | 'RC:Chatroom:Barrage' | 'RC:Chatroom:Like' | 'RC:Chatroom:SeatsCtrl' | 'RC:Chatroom:ChatBan' | 'RC:Chatroom:SeatApply',
- content: any,
- }
- type MessageEvent = {
- messages: MessageProps[],
- }
- const Events = RongIMLib.Events
- /**
- * 监听消息通知
- */
- const { MESSAGES, ...RestMessage } = Events
- RongIMLib.addEventListener(Events.MESSAGES, (evt: MessageEvent) => {
- console.log(evt, '收到消息')
- const { messages } = evt
- for (const message of messages) {
- // console.log(LIVE_EVENT_MESSAGE[message.messageType], message)
- if (LIVE_EVENT_MESSAGE[message.messageType]) {
- event.emit(LIVE_EVENT_MESSAGE[message.messageType], {...message.content, $EventMessage: message})
- }
- }
- })
- for (const Message of Object.values(RestMessage)) {
- RongIMLib.addEventListener(Message, (evt: any) => {
- console.log(Message, evt)
- // chatroomDestroyed
- event.emit(Message, {$EventMessage: null})
- })
- }
- /**
- * 监听 IM 连接状态变化
- */
- RongIMLib.addEventListener(Events.CONNECTING, () => {
- console.log('connecting')
- runtime.imConnectStatus = 'connecting'
- })
- RongIMLib.addEventListener(Events.CONNECTED, () => {
- console.log('connected')
- runtime.imConnectStatus = 'connected'
- })
- RongIMLib.addEventListener(Events.DISCONNECT, () => {
- console.log('disconnect')
- runtime.imConnectStatus = 'disconnect'
- })
- export const connectIM = async (imToken: string) => {
- try {
- const user = await RongIMLib.connect(imToken)
- runtime.rtcClient = RongIMLib.installPlugin(RTC.installer, {})
- console.log('connect success', user.data?.userId)
- return user
- } catch (error) {
- throw error
- }
- }
- /**
- * 设置声音
- * @param video
- * @param Value 声音大小
- */
- export const setVolume = (value: number) => {
- localStorage.setItem(AUDIO_DEVICE_VOLUME, value.toString())
- if(runtime.videoRef) {
- runtime.videoRef.volume = value / 100
- }
- // @ts-ignore
- if (runtime.activeTracks.microphone && runtime.activeTracks.microphone._element) {
- // @ts-ignore
- runtime.activeTracks.microphone._element.volume = value / 100
- }
- }
- /**
- * 设置video视频流
- */
- export const setVideoSrcObject = (video: HTMLVideoElement | null, mediaStreams: MediaStream | null) => {
- if (video && mediaStreams) {
- video.srcObject = mediaStreams
- video.onloadedmetadata = () => {
- video.play()
- }
- }
- }
- /**
- * 发起屏幕共享
- */
- export const shareScreenVideo = async () => {
- if (runtime.rtcClient && !runtime.screenShareStatus) {
- const screenTrack = await getTrack('screen')
- const oldTrack = runtime.activeTracks.camera as RTC.RCLocalTrack
- // removeTrack([oldTrack], 'camera')
- runtime.joinedRoom?.unpublish([oldTrack])
- setTrack([screenTrack as RTC.RCLocalTrack], 'screen')
- if (runtime.videoRef) {
- screenTrack.play(runtime.videoRef)
- runtime.screenShareStatus = true
- }
- screenTrack?.on(RTC.RCLocalTrack.EVENT_LOCAL_TRACK_END, (track: RTC.RCLocalTrack) => {
- runtime.screenShareStatus = false
- track.destroy()
- // removeTrack([track], 'screen')
- if (oldTrack) {
- setTrack([oldTrack as RTC.RCLocalTrack], 'camera')
- if (runtime.videoRef) {
- oldTrack.play(runtime.videoRef)
- }
- }
- // setVideoSrcObject(runtime.videoRef, this.mediaStreams)
- })
- }
- }
- /**
- *
- * 获取所有音频输入设备
- * @returns {Promise<void>}
- */
- export const getMicrophones = async () => {
- const microphones = await RTC.device.getMicrophones()
- runtime.microphones = microphones
- return microphones
- }
- /**
- *
- * 获取所有视频输入设备
- * @returns {Promise<void>}
- */
- export const getCameras = async () => {
- const cameras = await RTC.device.getCameras()
- runtime.cameras = cameras
- return cameras
- }
- /**
- *
- * 设置当前视频设备
- * @param camera MediaDeviceInfo
- */
- export const setSelectCamera = async (camera: MediaDeviceInfo) => {
- runtime.selectedCamera = camera
- localStorage.setItem(VIDEO_DEVICE_ID, camera.deviceId)
- const oldTrack = runtime.activeTracks.camera as RTC.RCLocalTrack
- if (oldTrack) {
- await removeTrack([oldTrack], 'camera', oldTrack.isPublished())
- }
- const track = await getTrack('camera')
- setTrack([track], 'camera', runtime.videoStatus === 'liveing')
- }
- /**
- *
- * 设置当前麦克风设备
- * @param microphone MediaDeviceInfo
- */
- export const setSelectMicrophone = async (microphone: MediaDeviceInfo) => {
- runtime.selectedMicrophone = microphone
- localStorage.setItem(AUDIO_DEVICE_ID, microphone.deviceId)
- const oldTrack = runtime.activeTracks.microphone as RTC.RCLocalTrack
- if (oldTrack) {
- await removeTrack([oldTrack], 'microphone', oldTrack.isPublished())
- }
- const track = await getTrack('microphone')
- setTrack([track], 'microphone', runtime.videoStatus === 'liveing')
- }
- type TrackResult = {
- code: RTC.RCRTCCode,
- track: RTC.RCMicphoneAudioTrack | RTC.RCCameraVideoTrack | RTC.RCScreenVideoTrack | undefined
- }
- export const getTrack = async (trackType: TrackType): Promise<RTC.RCLocalTrack> => {
- let res: TrackResult | undefined
- let Track: RTC.RCLocalTrack | null = null
- if (trackType === 'microphone') {
- res = await runtime.rtcClient?.createMicrophoneAudioTrack('RongCloudRTC', {
- micphoneId: runtime.selectedMicrophone?.deviceId,
- }) as TrackResult
- } else if (trackType === 'camera') {
- res = await runtime.rtcClient?.createCameraVideoTrack('RongCloudRTC', {
- cameraId: runtime.selectedCamera?.deviceId,
- faceMode: 'user',
- frameRate: RTC.RCFrameRate.FPS_24,
- resolution: RTC.RCResolution.W1920_H1080,
- }) as TrackResult
- } else {
- res = await runtime?.rtcClient?.createScreenVideoTrack() as TrackResult
- }
- Track = res?.track as RTC.RCLocalTrack
- if (trackType === 'camera' && !runtime.cameras.length) {
- runtime.deviceStatus[trackType] = 'none'
- } else if (trackType === 'microphone' && !runtime.microphones.length) {
- runtime.deviceStatus[trackType] = 'none'
- } else if (trackType === 'screen' && !runtime.screenShareStatus) {
- runtime.deviceStatus[trackType] = 'none'
- }
- if (res.code === RTC.RCRTCCode.PERMISSION_DENIED) {
- runtime.deviceStatus[trackType] = 'denied'
- } else {
- runtime.deviceStatus[trackType] = 'granted'
- }
- // if (res.code !== RTC.RCRTCCode.SUCCESS || !Track) {
- // throw new Error('获取数据流失败')
- // }
- return Track
- }
- /**
- * 添加视频流,会同步修改当先视频与推送的流
- * @param track
- */
- export const setTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackType, needPublish = true) => {
- for (const track of tracks) {
- // @ts-ignore
- // await runtime.mediaStreams?.addTrack(track._msTrack)
- if (trackType === 'microphone') {
- console.log('添加麦克风')
- track.play()
- }
- runtime.activeTracks[trackType] = track
- }
- if (needPublish) {
- // console.log('publish', runtime.joinedRoom)
- await runtime.joinedRoom?.publish(tracks)
- }
- }
- /**
- * 删除视频流,会同步修改当先视频与推送的流
- * @param track
- */
- export const removeTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackType, needPublish = true) => {
- if (needPublish) {
- await runtime.joinedRoom?.unpublish(tracks)
- }
- for (const track of tracks) {
- // @ts-ignore
- // await runtime.mediaStreams?.removeTrack(track._msTrack)
- // runtime.activeTracks[trackType].destroy()
- // console.log(runtime.activeTracks[trackType])
- track?.destroy()
- runtime.activeTracks[trackType] = null
- }
- }
- export const joinIMRoom = async (roomId: string, type: RTC.RCLivingType, listenEvents: RTC.IRoomEventListener | null) => {
- await RongIMLib.joinChatRoom(roomId, {count: -1})
- const join = await runtime.rtcClient?.joinLivingRoom(roomId, type)
- if (join?.code != RTC.RCRTCCode.SUCCESS) throw Error('加入房间失败')
- join.room?.registerRoomEventListener(listenEvents)
- return join
- }
- export const joinRoom = async (roomId: string, type: RTC.RCLivingType, listenEvents: RTC.IRoomEventListener | null) => {
- try {
- await request.get('/api-web/imLiveBroadcastRoom/joinRoom', {
- params: {
- roomUid: runtime.roomUid,
- userId: state.user?.speakerId,
- }
- })
- } catch (error) {}
- return await joinIMRoom(roomId, type, listenEvents)
- }
- /**
- * 开始直播
- */
- export const startLive = async (resetTime = true) => {
- if (runtime.videoStatus !== 'stream') throw Error('当前无视频流')
- const room = runtime.joinedRoom
- if (room) {
- // const microphoneAudioTrack = await getTrack('microphone')
- // const cameraVideoTrack = await getTrack('camera')
- // await setTrack([cameraVideoTrack], 'camera')
- // await setTrack([microphoneAudioTrack], 'microphone')
- // const builder = await runtime.joinedRoom?.getMCUConfigBuilder()
- // // @ts-ignore
- // await builder.setOutputVideoRenderMode?.(RTC.MixVideoRenderMode.WHOLE)
- // // @ts-ignore
- // await builder.flush()
- runtime.videoStatus = 'liveing'
- }
- if (resetTime) {
- sessionStorage.setItem(START_LIVE_TIME, dayjs().valueOf().toString())
- }
- sessionStorage.setItem(START_LIVE_STATUS, 'liveing')
- }
- /**
- * 关闭直播
- */
- export const closeLive = async () => {
- // removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack)
- // await request.post('/api-im/user/statusImUser', {
- // data: {
- // os: 'PC',
- // status: 3,
- // userId: state.user?.id,
- // }
- // })
- sessionStorage.removeItem(START_LIVE_TIME)
- sessionStorage.removeItem(START_LIVE_STATUS)
- runtime.videoStatus = 'stream'
- for (const key in runtime.activeTracks) {
- if (Object.prototype.hasOwnProperty.call(runtime.activeTracks, key)) {
- const track = runtime.activeTracks[key as TrackType] as RTC.RCLocalTrack
- if (track) {
- removeTrack([track], key as TrackType)
- }
- }
- }
- }
- /**
- * 同步点赞数量
- */
- export const loopSyncLike = async () => {
- if ((runtime.likeCount !== runtime.lastLikeCount || runtime.likeCount === 0) && state.user) {
- try {
- await request.get('/api-web/imLiveBroadcastRoom/syncLike', {
- hideLoading: true,
- hideMessage: true,
- params: {
- likeNum: runtime.likeCount,
- roomUid: runtime.roomUid,
- }
- })
- runtime.lastLikeCount = runtime.likeCount
- } catch (error) {}
- }
- setTimeout(() => {
- loopSyncLike()
- }, 1000 * 60)
- }
- type SendMessageType = 'text' | 'image' | 'audio' | 'video' | 'file' | 'SeatsCtrl' | 'ChatBan' | 'SeatApply' | 'SeatResponse'
- export const getSendMessageUser = () => {
- return {
- id: state.user?.speakerId,
- name: state.user?.speakerName,
- userId: state.user?.speakerId,
- userName: state.user?.speakerName,
- }
- }
- /**
- *
- * @param msg 消息内容
- * @param type 消息类型
- * @returns null 或者 发送消息的结果
- */
- export const sendMessage = async (msg: any, type: SendMessageType = 'text') => {
- let message: RongIMLib.BaseMessage<unknown> | null = null
- if (!msg) return
- const conversation = {
- conversationType: RongIMLib.ConversationType.CHATROOM,
- targetId: runtime.joinedRoom?._roomId as string,
- }
- if (type === 'text') {
- message = new RongIMLib.TextMessage({
- user: getSendMessageUser(),
- content: msg
- })
- } else if (type === 'SeatsCtrl') {
- message = new MessageSeatsCtrl(msg)
- } else if (type === 'ChatBan') {
- message = new MessageChatBan(msg)
- } else if (type === 'SeatApply') {
- message = new MessageSeatApply(msg)
- } else if (type === 'SeatResponse') {
- message = new MessageSeatResponse(msg)
- }
- if (!message) return
- console.log(message)
- return await RongIMLib.sendMessage(conversation, message)
- }
- export const openDevice = async (trackType: TrackType, needPublish = true) => {
- if (trackType === 'microphone' && runtime.activeTracks[trackType]) {
- runtime.activeTracks[trackType]?.unmute()
- } else {
- const track = await getTrack(trackType)
- await setTrack([track], trackType, needPublish)
- if (runtime.videoRef) {
- track.play(runtime.videoRef)
- }
- }
- }
- export const closeDevice = async (trackType: TrackType, needPublish = true) => {
- const track = runtime.activeTracks[trackType]
- if (trackType !== 'microphone') {
- // console.log('closeDevice', track)
- // track?.destroy()
- await removeTrack([track] as RTC.RCLocalTrack[], trackType, needPublish)
- } else {
- track?.mute()
- }
- }
- export const toggleDevice = async (trackType: TrackType) => {
- const track = runtime.activeTracks[trackType]
- const needPublish = runtime.videoStatus === 'liveing'
- if (track) {
- if (trackType === 'camera') {
- runtime.deviceStatus.camera = 'closed'
- }
- closeDevice(trackType, needPublish)
- } else {
- if (trackType === 'camera') {
- runtime.deviceStatus.camera = 'granted'
- }
- openDevice(trackType, needPublish)
- }
- }
- export const leaveIMRoom = async () => {
- await closeLive()
- if (runtime.joinedRoom) {
- // @ts-ignore
- await runtime.rtcClient?.leaveRoom(runtime.joinedRoom)
- runtime.joinedRoom = null
- }
- }
|