| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- import React, { useState, useEffect, useRef } from 'react';
- import AliRtcEngine, { AliRtcSubscribeState, AliRtcVideoTrack,AliRtcSdkChannelProfile } from 'aliyun-rtc-sdk';
- import { ToastContainer, toast } from 'react-toastify';
- // 辅助函数
- function hex(buffer: ArrayBuffer): string {
- const hexCodes = [];
- const view = new DataView(buffer);
- for (let i = 0; i < view.byteLength; i += 4) {
- const value = view.getUint32(i);
- const stringValue = value.toString(16);
- const padding = '00000000';
- const paddedValue = (padding + stringValue).slice(-padding.length);
- hexCodes.push(paddedValue);
- }
- return hexCodes.join('');
- }
- async function generateToken(
- appId: string,
- appKey: string,
- channelId: string,
- userId: string,
- timestamp: number
- ): Promise<string> {
- const encoder = new TextEncoder();
- const data = encoder.encode(`${appId}${appKey}${channelId}${userId}${timestamp}`);
- const hash = await crypto.subtle.digest('SHA-256', data);
- return hex(hash);
- }
- function showToast(type: 'info' | 'success' | 'error', message: string): void {
- switch(type) {
- case 'info':
- toast.info(message);
- break;
- case 'success':
- toast.success(message);
- break;
- case 'error':
- toast.error(message);
- break;
- }
- }
- const appId = 'a5842c2a-d94a-43be-81de-1fdb712476e1';
- const appKey = 'b71d65f4f84c450f6f058f4ad507bd42';
- export const RTCPage = () => {
- const [channelId, setChannelId] = useState<string>('');
- const [userId, setUserId] = useState<string>('');
- const [isJoined, setIsJoined] = useState<boolean>(false);
- const aliRtcEngine = useRef<AliRtcEngine | null>(null);
- const remoteVideoElMap = useRef<Record<string, HTMLVideoElement>>({});
- const remoteVideoContainer = useRef<HTMLDivElement>(null);
- function removeRemoteVideo(userId: string, type: 'camera' | 'screen' = 'camera') {
- const vid = `${type}_${userId}`;
- const el = remoteVideoElMap.current[vid];
- if (el) {
- aliRtcEngine.current!.setRemoteViewConfig(null, userId, type === 'camera' ? AliRtcVideoTrack.AliRtcVideoTrackCamera : AliRtcVideoTrack.AliRtcVideoTrackScreen);
- el.pause();
- remoteVideoContainer.current?.removeChild(el);
- delete remoteVideoElMap.current[vid];
- }
- }
- function listenEvents() {
- if (!aliRtcEngine.current) {
- return;
- }
- aliRtcEngine.current.on('remoteUserOnLineNotify', (userId: string, elapsed: number) => {
- console.log(`用户 ${userId} 加入频道,耗时 ${elapsed} 秒`);
- showToast('info', `用户 ${userId} 上线`);
- });
- aliRtcEngine.current.on('remoteUserOffLineNotify', (userId, reason) => {
- console.log(`用户 ${userId} 离开频道,原因码: ${reason}`);
- showToast('info', `用户 ${userId} 下线`);
- removeRemoteVideo(userId, 'camera');
- removeRemoteVideo(userId, 'screen');
- });
- aliRtcEngine.current.on('bye', (code) => {
- console.log(`bye, code=${code}`);
- showToast('info', `您已离开频道,原因码: ${code}`);
- });
- aliRtcEngine.current.on('videoSubscribeStateChanged', (
- userId: string,
- oldState: AliRtcSubscribeState,
- newState: AliRtcSubscribeState,
- interval: number,
- channelId: string
- ) => {
- console.log(`频道 ${channelId} 远端用户 ${userId} 订阅状态由 ${oldState} 变为 ${newState}`);
- const vid = `camera_${userId}`;
- if (newState === 3) {
- const video = document.createElement('video');
- video.autoplay = true;
- video.className = 'w-80 h-45 mr-2 mb-2 bg-black';
- remoteVideoElMap.current[vid] = video;
- remoteVideoContainer.current?.appendChild(video);
- aliRtcEngine.current!.setRemoteViewConfig(video, userId, AliRtcVideoTrack.AliRtcVideoTrackCamera);
- } else if (newState === 1) {
- removeRemoteVideo(userId, 'camera');
- }
- });
- aliRtcEngine.current.on('screenShareSubscribeStateChanged', (
- userId: string,
- oldState: AliRtcSubscribeState,
- newState: AliRtcSubscribeState,
- interval: number,
- channelId: string
- ) => {
- console.log(`频道 ${channelId} 远端用户 ${userId} 屏幕流的订阅状态由 ${oldState} 变为 ${newState}`);
- const vid = `screen_${userId}`;
- if (newState === 3) {
- const video = document.createElement('video');
- video.autoplay = true;
- video.className = 'w-80 h-45 mr-2 mb-2 bg-black';
- remoteVideoElMap.current[vid] = video;
- remoteVideoContainer.current?.appendChild(video);
- aliRtcEngine.current!.setRemoteViewConfig(video, userId, AliRtcVideoTrack.AliRtcVideoTrackScreen);
- } else if (newState === 1) {
- removeRemoteVideo(userId, 'screen');
- }
- });
- }
- const handleLoginSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- const timestamp = Math.floor(Date.now() / 1000) + 3600 * 3;
- if (!channelId || !userId) {
- showToast('error', '数据不完整');
- return;
- }
- const engine = AliRtcEngine.getInstance();
- aliRtcEngine.current = engine;
- listenEvents();
- try {
- const token = await generateToken(appId, appKey, channelId, userId, timestamp);
- aliRtcEngine.current!.setChannelProfile(AliRtcSdkChannelProfile.AliRtcSdkCommunication);
- await aliRtcEngine.current.joinChannel(
- {
- channelId,
- userId,
- appId,
- token,
- timestamp,
- },
- userId
- );
- showToast('success', '加入频道成功');
- setIsJoined(true);
- aliRtcEngine.current!.setLocalViewConfig('localPreviewer', AliRtcVideoTrack.AliRtcVideoTrackCamera);
- } catch (error) {
- console.log('加入频道失败', error);
- showToast('error', '加入频道失败');
- }
- };
- const handleLeaveClick = async () => {
- Object.keys(remoteVideoElMap.current).forEach(vid => {
- const arr = vid.split('_');
- removeRemoteVideo(arr[1], arr[0] as 'screen' | 'camera');
- });
- if (aliRtcEngine.current) {
- await aliRtcEngine.current.stopPreview();
- await aliRtcEngine.current.leaveChannel();
- aliRtcEngine.current.destroy();
- aliRtcEngine.current = null;
- }
- setIsJoined(false);
- showToast('info', '已离开频道');
- };
- useEffect(() => {
- AliRtcEngine.setLogLevel(0);
- }, []);
- return (
- <div className="container p-2">
- <h1 className="text-2xl font-bold mb-4">aliyun-rtc-sdk 快速开始</h1>
- <ToastContainer
- position="top-right"
- autoClose={5000}
- hideProgressBar={false}
- newestOnTop={false}
- closeOnClick
- rtl={false}
- pauseOnFocusLoss
- draggable
- pauseOnHover
- />
- <div className="flex flex-wrap -mx-2 mt-6">
- <div className="w-full md:w-1/2 px-2 mb-4">
- <form id="loginForm" onSubmit={handleLoginSubmit}>
- <div className="mb-2">
- <label htmlFor="channelId" className="block text-gray-700 text-sm font-bold mb-2">频道号</label>
- <input
- className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
- id="channelId"
- type="text"
- value={channelId}
- onChange={(e) => setChannelId(e.target.value)}
- />
- </div>
- <div className="mb-2">
- <label htmlFor="userId" className="block text-gray-700 text-sm font-bold mb-2">用户ID</label>
- <input
- className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
- id="userId"
- type="text"
- value={userId}
- onChange={(e) => setUserId(e.target.value)}
- />
- </div>
- <button
- id="joinBtn"
- type="submit"
- className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mb-2"
- disabled={isJoined}
- >
- 加入频道
- </button>
- <button
- id="leaveBtn"
- type="button"
- className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded mb-2"
- disabled={!isJoined}
- onClick={handleLeaveClick}
- >
- 离开频道
- </button>
- </form>
- <div className="mt-6">
- <h4 className="text-lg font-bold mb-2">本地预览</h4>
- <video
- id="localPreviewer"
- muted
- className="w-80 h-45 mr-2 mb-2 bg-black"
- ></video>
- </div>
- </div>
- <div className="w-full md:w-1/2 px-2">
- <h4 className="text-lg font-bold mb-2">远端用户</h4>
- <div id="remoteVideoContainer" ref={remoteVideoContainer}></div>
- </div>
- </div>
- </div>
- );
- };
-
|