安装依赖:
npm install aliyun-rtc-wx-sdk crypto-js
uni-app,新建一个页面,粘贴以下代码
在阿里云实时音视频补充appId、appKey即可,
<template>
<view class="container">
<!-- 用户输入区域 -->
<view class="input-section" v-if="!isJoined">
<uni-easyinput
v-model="channelId"
placeholder="请输入频道ID"
:clearable="true"
/>
<uni-easyinput
v-model="userName"
placeholder="请输入用户名"
:clearable="true"
/>
<uni-easyinput
v-model="userId"
placeholder="请输入用户ID"
:clearable="true"
/>
<button @click="joinChannel" type="primary">加入房间</button>
</view>
<!-- 直播控制区域 -->
<view class="control-section" v-if="isJoined">
<button @click="leaveChannel" type="warn">离开房间</button>
<button @click="switchCamera">切换摄像头</button>
<button @click="toggleMic">{{ pusher.enableMic ? '关闭麦克风' : '开启麦克风' }}</button>
<button @click="toggleCamera">{{ pusher.enableCamera ? '关闭摄像头' : '开启摄像头' }}</button>
</view>
<!-- 推流组件 -->
<live-pusher
ref="livePusher"
:url="pusher.url"
:mode="pusher.mode"
:autopush="pusher.autopush"
:beauty="pusher.beauty"
:whiteness="pusher.whiteness"
:muted="pusher.muted"
:mirror="pusher.mirror"
:local-mirror="pusher.localMirror"
:enable-camera="pusher.enableCamera"
:enable-mic="pusher.enableMic"
:remote-mirror="pusher.remoteMirror"
:enable-ans="pusher.enableAns"
:device-position="pusher.devicePosition"
:enable-agc="pusher.enableAgc"
@statechange="onPusherStateChange"
@netstatus="onPusherNetStatus"
@error="onPusherError"
style="width: calc((100vw - 40rpx)); height: calc((100vw - 40rpx)/4*3);"
v-if="pusher.url"
/>
拉流组件列表({{ playerList.length }}):
<view class="player-list" v-if="playerList.length > 0">
<view v-for="(player, index) in playerList" :key="index" class="player-item">
<live-player
:src="player.src"
mode="RTC"
autoplay
@statechange="onPlayerStateChange"
@netstatus="onPlayerNetStatus"
style="width: calc((50vw - 25rpx)); height: calc((50vw - 25rpx)/4*3);"
/>
<text>{{ player.userName || player.userId }}</text>
</view>
</view>
<!-- 调试信息 -->
<view class="debug-info">
<text>房间状态: {{ isJoined ? '已加入' : '未加入' }}</text>
<text>用户数量: {{ playerList.length }}</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, getCurrentInstance } from 'vue'
import { queryAndGetUserInfo, UserInfo, getUserInfo } from '@/utils/userInfo'
import * as MainApi from './mainApi'
import baseInfo from '@/utils/base'
import { pagesRecords } from '@/hooks/pageRecordsHooks'
// live-pusher 实例,用于切换摄像头
let LivePusher = uni.createLivePusherContext('livePusher')
import AliRtcEngine from "aliyun-rtc-wx-sdk";
import CryptoJS from 'crypto-js';
pagesRecords()
let currentInstance = ref()
const { ctx } = getCurrentInstance() as any
// 响应式数据
const pusher = ref({})
const playerList = ref([])
const isJoined = ref(false)
const userName = ref('')
const userId = ref('')
const appId = ref('')
const appKey = ref('')
const channelId = ref('AliRtcDemo')
let artc: any = null
// 生成Token
const generateToken = (appId: string, appKey: string, channelId: string, userId: string, timestamp: number) => {
const data = `${appId}${appKey}${channelId}${userId}${timestamp}`;
const hash = CryptoJS.SHA256(data);
return hash.toString(CryptoJS.enc.Hex);
}
// 初始化ARTC引擎
const initARTC = async () => {
try {
artc = AliRtcEngine.getInstance()
bindEvents()
uni.showToast({
title: 'ARTC引擎初始化成功',
icon: 'success'
})
} catch (error) {
console.error('ARTC引擎初始化失败:', error)
uni.showToast({
title: 'ARTC引擎初始化失败',
icon: 'error'
})
}
}
// 绑定事件监听
const bindEvents = () => {
// 远程用户上线通知
artc.on("remoteUserOnLineNotify", (user: any, newPlayerList: any[]) => {
uni.showToast({
title: `${user.displayName || user.userId} 加入了房间`,
icon: "none",
})
playerList.value = newPlayerList
})
// 远程用户下线通知
artc.on("remoteUserOffLineNotify", (user: any, newPlayerList: any[]) => {
uni.showToast({
title: `${user.displayName || user.userId} 退出了房间`,
icon: "none",
})
playerList.value = newPlayerList
})
// 远程轨道可用通知
artc.on("remoteTrackAvailableNotify", (user: any, newPlayerList: any[]) => {
playerList.value = newPlayerList
})
// 被踢出房间
artc.on("bye", () => {
uni.showToast({
title: "您已被踢出房间",
icon: "none",
})
pusher.value = {}
playerList.value = []
isJoined.value = false
})
}
// 加入频道
const joinChannel = async () => {
if (isJoined.value) {
uni.showToast({
title: '已在房间中',
icon: 'none'
})
return
}
// 检查用户输入
if (!userName.value.trim()) {
uni.showToast({
title: '请输入用户名',
icon: 'none'
})
return
}
if (!userId.value.trim()) {
uni.showToast({
title: '请输入用户ID',
icon: 'none'
})
return
}
uni.showLoading({
title: '加入房间中...'
})
try {
const timestamp = Math.floor(Date.now() / 1000) + 3600
const token = generateToken(appId.value, appKey.value, channelId.value, userId.value, timestamp)
const { pusherAttributes, playerList: newPlayerList } = await artc.joinChannel({
appId: appId.value,
appKey: appKey.value,
channelId: channelId.value,
userId: userId.value,
userName: userName.value,
token: token,
timestamp: timestamp
}, userName.value)
pusher.value = pusherAttributes
playerList.value = newPlayerList
isJoined.value = true
// 开始推流
artc.getPusherInstance().start()
uni.hideLoading()
uni.showToast({
title: '加入房间成功',
icon: 'success'
})
} catch (error) {
uni.hideLoading()
console.error('加入房间失败:', error)
uni.showToast({
title: '加入房间失败',
icon: 'error'
})
}
}
// 离开频道
const leaveChannel = async () => {
if (!isJoined.value) {
return
}
try {
await artc.leaveChannel()
pusher.value = {}
playerList.value = []
isJoined.value = false
uni.showToast({
title: '已离开房间',
icon: 'success'
})
} catch (error) {
console.error('离开房间失败:', error)
}
}
// 切换摄像头
const switchCamera = () => {
LivePusher.switchCamera()
}
// 切换麦克风
const toggleMic = () => {
pusher.value.enableMic = !pusher.value.enableMic
}
// 切换摄像头
const toggleCamera = () => {
pusher.value.enableCamera = !pusher.value.enableCamera
}
// live-pusher 状态变化处理
const onPusherStateChange = (e: any) => {
console.log("live-pusher code: ", e.detail.code)
artc.handlePusherStateChange(e)
}
// live-pusher 网络状态处理
const onPusherNetStatus = (e: any) => {
console.log("live-pusher netStatus: ", e.detail.info)
artc.handlePusherNetStatus(e)
}
// live-pusher 错误处理
const onPusherError = (e: any) => {
artc.handlePusherError(e)
}
// live-player 状态变化处理
const onPlayerStateChange = (e: any) => {
console.log("live-player code: ", e.detail.code)
artc.handlePlayerStateChange(e)
}
// live-player 网络状态处理
const onPlayerNetStatus = (e: any) => {
console.log("live-player netStatus: ", e.detail.info)
artc.handlePlayerNetStatus(e)
}
// 生命周期
onMounted(() => {
initARTC()
})
onUnmounted(() => {
leaveChannel()
})
</script>
<style scoped lang="scss">
.container {
padding: 20rpx;
}
.input-section {
margin-bottom: 30rpx;
.uni-easyinput {
margin-bottom: 20rpx;
}
button {
margin-top: 20rpx;
}
}
.control-section {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-bottom: 30rpx;
button {
font-size: 24rpx;
flex: 1;
padding: 0;
}
}
.player-list {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-top: 30rpx;
.player-item {
text-align: center;
text {
display: block;
margin-top: 10rpx;
font-size: 24rpx;
color: #666;
}
}
}
.debug-info {
margin-top: 30rpx;
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 10rpx;
text {
display: block;
margin-bottom: 10rpx;
font-size: 24rpx;
color: #333;
}
}
</style>
userName、userId随便写,不重复即可,想进入同一个频道就让channelId一致,