diff --git a/client/public/signaling-session.js b/client/public/signaling-session.js index cc5b86a..9b051d5 100644 --- a/client/public/signaling-session.js +++ b/client/public/signaling-session.js @@ -7,7 +7,7 @@ const INVITE_EVENT_NAMES = Object.freeze([ 'invite-failed' ]); -const DEFAULT_SOCKET_USER_NAME = 'Me'; +const DEFAULT_SOCKET_USER_NAME = '?'; const DEFAULT_SOCKET_USER_AVATAR = '/images/p1.png'; export function createSignalingInstance(useWebSocket) { diff --git a/client/public/store.js b/client/public/store.js index a6d3080..d478454 100644 --- a/client/public/store.js +++ b/client/public/store.js @@ -1,92 +1,49 @@ -/** - * 闂傚倸鍊搁崐鐑芥嚄閸撲礁鍨濇い鏍亹閳ь剨绠撳畷濂稿Ψ閵夛附袣闂備礁鎼粙渚€宕㈡總鍛婂€块柛顭戝亖娴滄粓鏌熸潏鍓у埌闁告梻鏁婚弻娑滅疀閹惧瓨鎷遍梺闈涙搐鐎氫即鐛Ο灏栧亾濞戞顏堝焵椤掍礁濮嶉柡? - * 婵犵數濮烽弫鎼佸磻閻樿绠垫い蹇撴缁€濠囨煃瑜滈崜姘辨崲濞戞瑥绶為悗锝庡亞椤︿即鎮楀▓鍨珮闁稿锕ユ穱濠囧醇閺囩偟鍊為梺瀹犮€€閸嬫挾绱掑Δ鈧ˇ闈涱潖濞差亝鐒婚柣鎰蔼鐎氭澘顭胯椤曨參鍩€椤掑喚娼愭繛娴嬫櫇閹广垹鈹戦崱鈺佹闂佸湱铏庨崰妤呭磻閹邦喒鍋撶憴鍕婵炶绠戦埢?Observable 濠电姷鏁告慨鐑姐€傞挊澹╋綁宕ㄩ弶鎴濈€銈呯箰閻楀棝鎮為崹顐犱簻闁瑰搫妫楁禍鍓х磼閸撗嗘闁告ɑ鍎抽埥澶愭偨缁嬭法鍔﹀銈嗗笒鐎氼參鍩涢幋锔界厵缂佸瀵ч幑锝夋煃閽樺妯€闁哄被鍊曠叅閻犲洩灏欐禒鎾⒑闂堟稒澶勯柛鏃€鐟ラ锝嗙節濮橆儵銊╂煏婢跺牆鍔氱紒渚囧櫍濮婄粯鎷呴崨濠呯濡炪値鍘奸悧濠囥€冮妷鈺佸窛濠电姴鍟崝鍛節閻㈤潧孝闁稿﹪娼у嵄?Redux/Vuex/Pinia - */ import { mockCallSession } from './models.js'; -import { RenderStreaming } from "../../module/renderstreaming.js"; // WebRTC闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺鍝勬缁捇骞冨Δ鍛棃婵炴垶鐟﹂崰鎰箾閹寸偞灏紒澶婄秺瀵濡搁妷銏☆潔濠碘槅鍨拃锔界妤e啯鈷? -import { getServerConfig, getRTCConfiguration } from "../js/config.js";//闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢敂钘変罕闂佺硶鍓濋悷褔鎯岄幘缁樺€垫繛鎴烆伆閹达箑鐭楅煫鍥ㄧ⊕閻撶喖鏌¢崘銊モ偓鍝ユ暜閸洘鈷掗柛灞诲€曢悘锕傛煛鐏炵偓绀冪紒缁樼洴閹瑩顢楁担鍝勭到闂備焦鐪归崺鍕垂閹殿喖顥氭い鎾跺У椤洟鏌熼幑鎰靛殭缂佲偓閸愵喗鐓欐い鏍ф鐎氼喗绂嶉鍫熲拻濞达絽鎽滅粔鐑樸亜閵夛附灏柍璇茬Ч瀹曠娀鎯勭€n喗鈷掗柛灞捐壘閳ь剛鍏橀幃鐐烘晝閳ь剟鈥﹂崹顔ョ喖鎳栭埡鍐帬闂備礁澹婇崑鍛哄鈧畷? -import { showNotification, generateId } from './utils.js'; // 闂傚倸鍊峰ù鍥敋瑜嶉湁闁绘垼妫勯弸渚€鏌熼梻瀵割槮闁稿被鍔庨幉鎼佸棘鐠恒劍娈鹃梺鎸庣箓椤︻垶鐛姀锛勭闁瑰鍋熼幊鍛存煕閺冨牊娑ч柍瑙勫灴椤㈡瑧绮电€n剙濮煎┑鐐茬摠缁娀宕滈悢椋庢殾闁挎繂顦悞鍨亜閹烘垵顏柍閿嬪灴閺屾稑鈽夊鍫濅紣闂佽绻愬畷顒勫煘閹达富鏁婇柛婵嗗閸嬫挸鈹戦崱鈺佹? +import { RenderStreaming } from "../../module/renderstreaming.js"; +import { getServerConfig, getRTCConfiguration } from "../js/config.js"; +import { showNotification, generateId } from './utils.js'; import chatMessage from './chatmessage.js'; -import { - DEFAULT_PARTICIPANT_AVATAR, - DEFAULT_PARTICIPANT_NAME, - buildParticipantsSyncData, - omitParticipant, - removeParticipant, - upsertParticipant -} from './participants.js'; -import { - AUDIO_CONFIG, - VAD_CONFIG, - VIDEO_ONLY_CONSTRAINT, - buildVideoConstraints, - getAdaptiveVideoBitrate, - getResolutionLabel, - getTargetResolutionBitrate -} from './media-config.js'; +import { DEFAULT_PARTICIPANT_AVATAR, DEFAULT_PARTICIPANT_NAME, buildParticipantsSyncData, omitParticipant, removeParticipant, upsertParticipant } from './participants.js'; +import { AUDIO_CONFIG, VAD_CONFIG, VIDEO_ONLY_CONSTRAINT, buildVideoConstraints, getAdaptiveVideoBitrate, getResolutionLabel, getTargetResolutionBitrate } from './media-config.js'; import { buildStatsLogPayload, createAudioAnalyser, getAudioLevel } from './media-monitoring.js'; -import { - bindInviteSocketEvents, - buildSocketUserInfoPayload, - createSignalingInstance, - ensureSignalingStarted, - getActiveSignalingInstance, - sendInviteSignal, - sendSocketUserInfo -} from './signaling-session.js'; +import { bindInviteSocketEvents, buildSocketUserInfoPayload, createSignalingInstance, ensureSignalingStarted, getActiveSignalingInstance, sendInviteSignal, sendSocketUserInfo } from './signaling-session.js'; import { getNetworkQualityFromSummary, summarizeInboundStats } from './webrtc-stats.js'; - class CallStateManager { constructor() { - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢妶鍥╃厠闂佸壊鍋呭ú宥夊焵椤掑﹦鐣电€规洖銈告慨鈧柕蹇嬪灩椤︹晛鈹戞幊閸娧呭緤娴犲鐤い鏍仜绾惧鏌熼幍顔碱暭闁绘挾濞€閺屾稑鈹戦崟顐㈠Ф闂佸搫妫崹鍫曞蓟? this.state = { id: generateId(), session: { ...mockCallSession, - status: 'idle' // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹濠德板€曢崯顖氱暦閺屻儲鐓曠€光偓閳ь剟宕曢幋鐘电闁哄稁鍘介悡娆撴煙濞堝灝鏋涙い锝呫偢閺屾稓鈧綆鍋勬慨宥夋煛鐏炶鈧繂鐣烽锕€唯妞ゆ棁濮ら惁婊堟⒒娴d警鐒炬い鎴濇楠炴劖绻濆顒傤唵闂佸憡绋戦悺銊╁磻椤忓懌浜滈柡宥冨妿閻倖淇婇幓鎺撹础缂佽鲸鎹囧畷鎺戔枎閹搭厽袦闂備礁鎼悧婊堝礈濞嗘挴鈧棃宕橀钘夌檮婵? + status: 'idle' }, - localStream: null, // MediaStream 闂傚倸鍊峰ù鍥敋瑜嶉湁闁绘垼妫勯弸渚€鏌熼梻鎾闁逞屽厸閻掞妇鎹㈠┑瀣倞闁靛鍎冲Ο? - remoteStream: null, // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩顔瑰亾閸愵喖骞㈡俊鐐存礀閹碱偊鍩為幋鐘亾閿濆骸浜滈柛鎾崇秺濮婅櫣绱掑Ο鍝勑曢梺鍛婃尰绾板秶绮嬪澶娢ч柛鈩冪懅閻﹀牊绻濋悽闈浶㈤柛濠咁潐閸掑﹪鎮¢獮鐔风秺閹剝鎯旈埦鈧崑鎾诲捶椤撶喎搴婂┑鐐村灦濮樸劎绮堥崟顖涚厽婵☆垰鍚嬮弳鈺呮煟閳╁喚鐒界紒杈ㄦ尰閹峰懏绂掔€n亝鎳欓梻浣藉吹閸熷潡寮插☉銏″仼鐎瑰嫭瀚堥弮鍫濆窛妞ゆ棃妫跨花鐢告⒑閻熸澘鎮戦柟顖氱焸瀹曚即寮介鐐垫煣闂佹寧绻傞ˇ浼村煕閹达附鐓曟繝闈涙椤忊晠鏌℃担鐟扳枙闁哄本鐩俊鐑筋敍濠婂啫鐓傚┑鐐茬摠缁挾绮婚弽顓炶摕闁跨喓濮寸粈瀣亜閹扳晛鈧顢欐繝鍥ㄧ厽闁靛繆鏅涢悘鐘充繆椤愶絿绠炵€殿喖顭锋俊鎼佸Ψ閵忊槅娼旀繝娈垮枟椤ㄥ懎螞濡ゅ懎鏋佺紒顖滄畽ticipant缂傚倸鍊搁崐鎼佸磹閻戣姤鍊块柨鏇楀亾閾荤偤鐓崶銊р槈闁搞劌鍊归妵鍕冀閵娧佲偓鎺旂磼閻樿崵鐣洪柡灞剧洴婵$兘顢欓悡搴浇闂備胶顭堥鍐礉瀹ュ桅闁告洦鍨奸弫鍥煟閺冨牜妫戞い鎴濆€荤槐鎾存媴閹绘帊澹? - remoteStreams: {}, // 婵犵數濮烽弫鍛婃叏娴兼潙鍨傚┑鍌滎焾閺勩儵鏌″鍐ㄥ闁崇懓绉电换娑橆啅椤旇崵鍑归梺绋垮椤ㄥ﹪寮婚敐澶婄疀闂傚牊绋戦~顏呯箾鐎涙ê娈犻柛濠冪墱閹广垹鈹戦崶鈺冪槇闂佺鏈粙鎴︻敁濞戞瑧绠鹃悗鐢殿焾鐢爼鏌涙繝鍌涜础闁?Map: { connectionId: MediaStream }闂傚倸鍊搁崐鐑芥倿閿旈敮鍋撶粭娑樻噽閻瑩鏌熺€涙绠ラ柣鎺曞Г缁绘繈寮撮悩铏彎t缂傚倸鍊搁崐鎼佸磹閻戣姤鍊块柨鏇楀亾閾荤偤鐓崶銊р槈闁搞劌鍊归妵鍕冀閵娧佲偓鎺旂磼閻樿崵鐣洪柡灞剧洴婵$兘顢欓悡搴浇闂備胶顭堥鍐礉瀹ュ桅闁告洦鍨奸弫鍥煟閺冨牜妫戞い鎴濆€荤槐鎾存媴閹绘帊澹? - participants: {} // 婵犵數濮烽弫鍛婃叏娴兼潙鍨傚┑鍌滎焾閺勩儵鏌″搴″箺闁稿鍊楅埀顒傛嚀鐎氼參顢楅悷鍍玞ipant闂傚倸鍊搁崐鐑芥倿閿曗偓椤啴宕归鍛姺闂佺鍕垫當缂佲偓婢跺备鍋撻獮鍨姎妞わ富鍨跺浼村Ψ閿斿墽顔曢梺鐟邦嚟閸嬬喖骞婇崨瀛樼厓闁荤喐澹嗘晥闂佸搫鑻粔褰掑春閳╁啯濯撮柛娑橈攻椤撹法绱?Map: { participantId: { id, name, avatar, mediaState, status } }闂傚倸鍊搁崐鐑芥倿閿旈敮鍋撶粭娑樻噽閻瑩鏌熺€涙绠ラ柣鎺曞Г缁绘繈寮撮悩铏彎t缂傚倸鍊搁崐鎼佸磹閻戣姤鍊块柨鏇楀亾閾荤偤鐓崶銊р槈闁搞劌鍊归妵鍕冀閵娧佲偓鎺旂磼閻樿崵鐣洪柡灞剧洴婵$兘顢欓悡搴浇闂備胶顭堥鍐礉瀹ュ桅闁告洦鍨奸弫鍥煟閺冨牜妫戞い鎴濆€荤槐鎾存媴閹绘帊澹? + localStream: null, + remoteStream: null, + remoteStreams: {}, + participants: {} }; - this.listeners = []; this.socketEventHandlers = {}; this._inviteEventSignaling = null; } - - // 闂傚倸鍊峰ù鍥х暦閸偅鍙忛柟鎯板Г閳锋梻鈧箍鍎遍ˇ顖炲垂閸岀偞鐓㈡俊顖滃皑缁辨岸鏌ㄥ┑鍡╂Ц缂佲偓鐎n偁浜滈柡宥冨妿閳藉霉濠婂啰绉烘慨濠冩そ閹剝鎯旈姀鈥虫瀾婵$偑鍊х徊鑲╁垝濞嗗繒鏆﹂柟杈剧畱缁犲鎮楀☉娅亪顢撻幘鍓佺=濞达絽澹婇崕鎾寸箾婢跺绀堢紒顔芥椤㈡岸鍩€椤掆偓椤? subscribe(callback) { this.listeners.push(callback); return () => { this.listeners = this.listeners.filter(cb => cb !== callback); }; } - - // 闂傚倸鍊搁崐鎼佸磹妞嬪孩顐介柨鐔哄Т绾惧鏌涘☉鍗炲季婵炲皷鏅犻弻鏇熺箾閻愵剚鐝曢梺绋款儏閸婂潡寮婚妸鈺傚亜闁告繂瀚呴姀銏㈢<闁绘﹩鍠栭崝锕傛煛鐏炵晫啸妞ぱ傜窔閺屾盯骞樼捄鐑樼€诲銈嗘穿缂嶄礁鐣疯ぐ鎺濇晝闁靛繈鍨婚悰顕€姊虹拠鑼闁稿绋掗弲鑸电鐎n偅娅栭梺鎼炲労閸撴岸鍩涢幋锔界厱婵炴垶锕銉╂煕閵堝骸澧撮柡灞剧洴楠炴鈧潧鎲¢崳褔姊? notify(changes) { this.listeners.forEach(cb => cb(this.state, changes)); } - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹濠德板€曢崯顖氱暦閺屻儲鐓曠€光偓閳ь剟宕曢幋鐘电闁哄稁鍘介悡娆撴煟濡も偓閻楀﹦娆㈤懠顒傜<闁? async init() { - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹濠德板€曢崯顖氱暦閺屻儲鐓曠€光偓閳ь剟宕曢幋鐘电闁哄稁鍘介悡娆撴煟濡も偓閻楀﹦娆㈤懠顒傜<闁逞屽墮閻f繈宕熼鍌氬箰闁诲骸绠嶉崕杈殽閹间胶宓侀柡宥庡幗閸嬨劍銇勯弽銊︾殤闁挎稑绉剁槐? await this.setupConfig(); - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鍨鹃幇浣圭稁缂傚倷鐒﹁摫闁告瑥绻橀弻鐔碱敍閿濆洣姹楅悷婊呭鐢帡鎮欐繝鍐︿簻闁瑰搫绉堕ˇ锕€霉閻樿櫕銇濇慨濠冩そ濡啫鈽夋潏鈺佸綃缂傚倷鑳舵慨鐢稿垂閸ф绠栭柨鐔哄Т閸楁娊鏌曡箛濞惧亾閸忓懎瀵叉繝鐢靛仩閹活亞寰婇崸妞烩偓锕傚醇閵夈儳鍘遍梺鍝勫暙閻楀﹪鎮? this.loadUserSettings(); - // 闂傚倸鍊搁崐椋庣矆娓氣偓瀹曘儳鈧綆鍠栫壕鍧楁煙閹増顥夐幖鏉戯躬閺屻倝鎳濋幍顔肩墯婵炲瓨绮岀紞濠囧蓟濞戙垹唯妞ゆ梻鍘ч~鈺呮煟鎼淬垼澹樻い锔垮嵆婵$敻宕熼姘鳖唺闂佺硶鍓濋妵鐐寸珶閺囩喓绡€闁汇垽娼цⅴ闂佺顑嗛幑鍥蓟閻旇櫣纾奸柕蹇曞У閻忓牏绱撴担鍝勑f俊鐐扮矙瀵鈽夐姀鐘插祮闂侀潧顭堥崕鎵姳婵犳碍鈷戦柛娑橈攻鐏忕敻鏌涘Ο鐘叉礌閳ь剨绠撳畷绋课旈埀顒傜矆閸縿鈧帒顫濋浣割槱濡炪們鍎查幐鎼佲€旈崘顔嘉ч幖绮光偓鑼泿婵$偑鍊栭崹闈浳涘┑瀣瀬鐎广儱顦粻娑㈡煛婢跺孩纭舵い鎾存そ濮婅櫣绱掑Ο鍝勵潕闂佺顑戠紞浣哥暦? await this.getLocalStream(); } - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鍨鹃幇浣圭稁缂傚倷鐒﹁摫闁告瑥绻橀弻鐔碱敍閿濆洣姹楅悷婊呭鐢帡鎮欐繝鍐︿簻闁瑰搫绉堕ˇ锕€霉閻樿櫕銇濇慨濠冩そ濡啫鈽夋潏鈺佸綃缂傚倷鑳舵慨鐢稿垂閸ф绠栭柨鐔哄Т閸楁娊鏌曡箛濞惧亾閸忓懎瀵叉繝鐢靛仩閹活亞寰婇崸妞烩偓锕傚醇閵夈儳鍘遍梺鍝勫暙閻楀﹪鎮? loadUserSettings() { const userSettings = localStorage.getItem('userSettings'); if (userSettings) { try { const settings = JSON.parse(userSettings); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢埛姘そ婵¤埖寰勭€n亙妲愰梻渚€娼ц墝闁哄懏鐩幏鎴︽偄鐏忎焦鏂€闂佺粯锚瀵爼骞栭幇鐗堝€垫慨姗嗗墻濡插綊鏌曢崶褍顏鐐村浮瀹曞崬顪冮幆褜妫滄繝鐢靛Х閺佸憡鎱ㄩ銏犵;闁瑰墽绮悡娆戠磽娴i潧鐏╅柡瀣枛閺屾稓鈧絻鍔屾慨鍌炴煛鐏炵偓绀冪紒缁樼箚缁犳盯寮撮悩铏啅闂傚倷鑳剁划顖炲箰鐠囪娲冀椤掆偓缁插綊姊绘担瑙勫仩闁稿氦宕靛濠囨嚍閵夛附鐝烽梺鍦帛瀹? if (settings.name || settings.avatar) { this.state.session.localUser = { ...this.state.session.localUser, @@ -94,17 +51,14 @@ class CallStateManager { name: settings.name || this.state.session.localUser.name, avatar: settings.avatar || this.state.session.localUser.avatar }; - - // 闂傚倸鍊搁崐鎼佸磹妞嬪孩顐介柨鐔哄Т绾惧鏌涘☉鍗炲季婵炲皷鏅犻弻鏇熺箾閻愵剚鐝曢梺绋款儏閸婂綊濡甸崟顖氱労闁告劏鏅滅欢婊堟⒒閸屾艾鈧绮堟笟鈧獮鏍敃閳锋碍妞芥俊鑸靛緞鐎n亙妲愰梻渚€娼ц墝闁哄懏鐩幏? this.notify({ type: 'USER_SETTINGS_UPDATED', user: this.state.session.localUser }); } - - // 闂傚倸鍊搁崐宄懊归崶顒夋晪闁哄稁鍘奸崒銊ф喐閻楀牆绗掗柛銊ュ€婚幉鎼佹偋閸繂鎯為梺鎼炲労閸撴瑩鎯屽Δ鈧…璺ㄦ崉閸濆嫷妲甸梺琛″亾闁告劖绁撮弨浠嬫煟濡偐甯涙繛鎳峰嫪绻嗘い鎰剁悼濞插瓨顨ラ悙鎼疁闁诡喒鏅濈槐鎺懳熼悡搴$闂傚倷绶氬褔鈥﹂崼銉ョ?闂侇剙绋侀弫鍌炴煃閸濆嫭鍣洪柣鎾存礋閺屾洘绻涢崹顔煎濡炪倖甯囬崹铏规崲濞戙垺鍋傞幖杈剧悼椤旀帗绻濈喊妯哄⒉闁烩剝娲熻棟闁哄被鍎查悡鐘绘煟閹寸伝顏堝煝閺囥垺鐓欐い鏃€顑欏鎰版煙瀹勭増鍣界紒顔界懄閹棃骞橀弶鎴犳Ш闂? if (settings.resolution) { this._savedResolution = settings.resolution; - console.log(`闂傚倷娴囬褎顨ョ粙鍖¤€块梺顒€绉埀顒婄畵瀹曠厧鈹戦幇顒侇吙闂備胶鍘ч幗婊堝极閹间降鈧懘鏌ㄧ€c劋绨婚梺鍝勫暙濞诧箓藟婢跺瞼纾奸柛鎾茬娴犙囨煃瑜滈崜娆戠不瀹ュ纾块梺顒€绉寸粻鐘诲箹濞n剙濡介柛濠囨涧閳规垿鎮╃€圭姴顥濈紓浣哄珡閸ャ劎鍘卞銈嗗姧缁插潡鍩ユ径濞炬斀闂勫洤鈻旈弴銏犵劦妞ゆ帒鍠氬鎰版煟閳╁啯绀嬬€规洘鍨块獮鍥级鐠侯煈鍞甸梺璇插嚱缂嶅棝宕伴弽顐ょ焼闁割偁鍨洪崰鎰扮叓閸ャ劎鈽夐柛? ${settings.resolution.width}x${settings.resolution.height}`); + console.log(`已恢复分辨率设置: ${settings.resolution.width}x${settings.resolution.height}`); } - } catch (error) { + } + catch (error) { console.error('Error loading user settings:', error); } } @@ -113,115 +67,85 @@ class CallStateManager { const res = await getServerConfig(); this.useWebSocket = res.useWebSocket; } - // 闂傚倸鍊搁崐椋庣矆娓氣偓瀹曘儳鈧綆鍠栫壕鍧楁煙閹増顥夐幖鏉戯躬閺屻倝鎳濋幍顔肩墯婵炲瓨绮岀紞濠囧蓟濞戙垹唯妞ゆ梻鍘ч~鈺呮煟鎼淬垼澹樻い锔垮嵆婵$敻宕熼姘鳖唺闂佺硶鍓濋妵鐐寸珶閺囩喓绡€闁汇垽娼цⅴ闂佺顑嗛幑鍥蓟閻旇櫣纾奸柕蹇曞У閻忓牏绱撴担鍝勑f俊鐐扮矙瀵鈽夐姀鐘插祮闂侀潧顭堥崕鎵姳婵犳碍鈷戦柛娑橈攻鐏忕敻鏌涘Ο鐘叉礌閳ь剨绠撳畷绋课旈埀顒傜矆閸縿鈧帒顫濋浣割槱濡炪們鍎查幐鎼佲€旈崘顔嘉ч幖绮光偓鑼泿婵$偑鍊栭崹闈浳涘┑瀣瀬鐎广儱顦粻娑㈡煛婢跺孩纭舵い鎾存そ濮婅櫣绱掑Ο鍝勵潕闂佺顑戠紞浣哥暦? async getLocalStream() { try { console.log('Requesting camera permission...'); - - // 濠电姷鏁告慨鐑姐€傞挊澹╋綁宕ㄩ弶鎴狅紱闂侀€炲苯澧撮柡灞剧〒閳ь剨缍嗛崑鍛暦瀹€鍕厸鐎光偓鐎n剛锛熸繛瀵稿婵″洭骞忛悩璇茬闁圭儤鍩堝銉х磽閸屾艾鈧嘲顪冮幒鎳ㄥ綊宕惰閺嗭箓鏌i姀銏╃劸缂佺姷鍠栭弻鐔虹磼閵忕姵鐏堥梻浣稿船濞诧妇鎹㈠┑瀣棃婵炴垶鐟ョ粣娑㈡偡濠婂懎顣肩紒顔芥尭椤繐煤椤忓嫪绱堕梺鍛婃处閸嬫帗瀵煎畝鍕拺闁硅偐鍋涙俊娲煕濡や礁鈻曢柣娑卞櫍瀹曞崬螣閼测晜鍤岄梻渚€鈧偛鑻晶顔姐亜閺囶亞绉┑鈩冩倐閸┾剝绻濋崘鈺傜彨濠电姷鏁搁崑鐐哄垂閸洍鈧箓宕堕‖顒婄悼缁艾顕欑粊宥秙erMedia if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { console.error('getUserMedia is not supported'); throw new Error('getUserMedia is not supported'); } - - // 闂傚倸鍊峰ù鍥х暦閸偅鍙忛柡澶嬪殮濞差亶鏁囬柕蹇曞Х閸濇姊绘笟鍥у缂佸鏁诲畷鏇㈠箣濠㈡繂缍婂畷妤呭礂閼测晝鈻忕紓鍌欒兌婵潧顪冩禒瀣摕婵炴垯鍨归崡鎶芥煏婵炲灝鍔欑紒銊ヮ煼濮婃椽宕ㄦ繝鍐ㄦ殫闂佸憡顭嗛崶锝傚亾閿旂偓宕夐柕濠忕畱绾绢垶姊洪幆褏绠烘い顐㈩樀楠炲鎮℃惔妯绘杸闂佺粯鍔欏褏鏁崼鏇熺厽婵°倓鐒︾亸顓熴亜椤愩垻绠茬紒鍌涘笧閳ь剨缍嗘禍鐐烘偪閳ь剚淇婇悙顏勨偓鏍暜閹烘鐤い鎰堕檮閸嬬喐銇勯弽顐沪闁抽攱鍨块弻娑滅疀閺囩偛浠橀梺鍛婃煟閸婃繈寮诲☉銏狀潊闁挎繂鎳撻弫鍧楁⒑闂堟稒澶勯柛鏃€鐟ラ悾鐑藉醇閺囩倣鈺呮煏婢跺牆鍔ゅù婊勭墵濮婄粯鎷呴悜妯烘畬闂佸湱鈷堥崑鍡欏垝閺冨牆绀堝ù锝堟閻撳姊虹粙璺ㄧ闁稿鍔楃划濠氬礈瑜忕壕浠嬫煕椤愮姴鐏╅崯鍝ョ磽娴e湱鈽夌紒缁樺笧濡叉劙骞掗幘瀵哥Ф闂侀潧臎閸涱垱婢栭梺璇叉唉椤煤韫囨稑纾块柣銏℃偠閳ь兛绀侀埥澶婎潨閸℃瑥寮抽梻浣告啞濞诧箓宕滃棰濇晣鐟滅増甯楅埛鎺懨归敐鍫綈闁稿濞€閺屾稒鎯旈姀鐘灆閻庤娲橀崹鐢稿煡婢舵劕顫呴柍鈺佸暞閻濐偊姊绘担鍝ョШ闁稿锕畷妤€螣娴f洩缍佸畷濂稿Ψ閿旀儳骞堟俊鐐€栭崝妤佹叏閹绢喖绀夋繝濠傜墛閻? - // 婵犵數濮烽弫鎼佸磻閻樿绠垫い蹇撴缁€濠囨煃瑜滈崜姘辨崲濞戞瑥绶為悗锝庡亞椤︿即鎮楀▓鍨珮闁稿锕ユ穱濠囧醇閺囩偛鑰垮┑鐐叉閺堫剟寮崹顐ょ瘈闁汇垽娼ч埢鍫熺箾娴e啿鍚樺☉妯锋闁靛繒濮烽鎴︽⒑缂佹﹩鐒界紒顕呭灦閹繝鎮㈤崗鑲╁帾婵犵數鍋涢悘婵嬪礉濮橆厹浜滈煫鍥э攻濞呭﹪鏌$仦鍓ф创濠碘剝鎮傛俊鐑藉Ψ椤旂厧唯濠电姷鏁搁崑鐔煎储瑜忛幑銏犖熺拋宕囩畾濠殿喗绻傞惌鍫澪f繝姘拺闁兼祴鏅涢崝瀣攽閻愯韬€殿喖顭锋俊鎼佸Ψ閵忊槅娼旀繝纰樻閸ㄦ娊宕㈣閵嗗倿宕崟鍨瘜闂侀潧鐗嗛崐褰掑汲濮椻偓閺屾盯濡烽幋婵嗘殶鐟滄澘顦埞鎴︽偐椤旇偐浼囬梺绯曟櫆閻楁粎鍒掑▎鎾崇闁告挷鑳堕悞濂告⒑缁洖澧茬紒瀣浮閹繝濡烽敂鍓ь啎闂佺懓顕崕鎰版倿妤e啯鍊垫慨姗嗗亜瀹撳棝鏌″畝鈧崰鏍€佸☉妯锋瀻闁瑰濮峰畷鏌ユ煟鎼淬値娼愭繛鍙夛耿閹虫繃銈i崘銊у幒闁瑰吋鐣崹娲磿閻斿吋鐓冮梺娆惧灠娴滈箖姊洪崫鍕靛剰缂佺粯锚椤繘宕崝鍊熸缁辨帒螣鐞涒剝鐎奸梻鍌欑閹测€愁潖瑜版帒鍨傜憸鐗堝笒缁€鍡涙煙閻戞﹩娈㈤柡浣哥У缁绘繃绻濋崒姘间患濡炪倕瀛╅惄顖氼潖濞差亝顥堟繛鎴炶壘椤e搫鈹戦悙鑼勾闁稿﹥绻堥妴浣糕枎閹邦喚鐦堥梺鎼炲劘閸斿酣宕? const videoConstraints = buildVideoConstraints(this._savedResolution); const stream = await navigator.mediaDevices.getUserMedia({ video: videoConstraints, audio: AUDIO_CONFIG }); - console.log('Stream obtained successfully:', stream); console.log('Video tracks:', stream.getVideoTracks()); console.log('Audio tracks:', stream.getAudioTracks()); - this.state.localStream = stream; this.state.session.localUser.mediaState.video = true; this.state.session.localUser.mediaState.audio = true; - console.log('Local stream stored, notifying UI...'); - - // 闂傚倸鍊搁崐鐑芥嚄閸洍鈧箓宕奸姀鈥冲簥闂佽澹嗘晶妤呭磻鐎n喗鐓欓柟瑙勫姦閸ゆ瑩鏌i幒鎴犱粵闁靛洤瀚伴獮鎺戭吋閸パ冾瀴闂備焦鐪归崝宥夊垂閸ф钃熼柨婵嗩槸缁犲ジ鏌涢弴銊ュ箻闁挎稓鍠栧娲偡閺夎法楠囬梺鍦焾閸熷潡鎮鹃悜钘夌闁绘劏鏅滈~宥夋煛婢跺﹦澧戦柛鏂跨Ч椤㈡瑩寮埀顒傛崲濠靛鍋ㄩ梻鍫熺◥閹撮绱撴担铏瑰笡缂佽瀚崚鎺楀醇閻旇櫣鎳濋梺閫炲苯澧柣锝呭槻閳诲酣骞樼€涙ɑ顏熼梻浣虹帛閿氶柛鐔风仢閳绘捁顦归柟顔筋殜閹兘寮跺▎鍙ョ棯婵犵數濮伴崹娲€﹂崶褜鍤? this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream }); - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼槷闂佸搫娲ㄦ慨鐑芥儗閹捐埖鍠愰柣妤€鐗嗙粭鎺楁煟閹烘垹浠涢柕鍥у楠炴帒顓奸崶褍顬夐梻浣圭湽閸斿秹宕归崸妤€钃熼柨婵嗩槸缁犵粯銇勯弽銊ㄥ闁冲嘲顦靛娲川婵犲倻鐟ㄩ梺鎸庢处娴滎亪鎮伴鈧獮鎺楀箣椤撶偞鍊梻浣规偠閸庮噣寮埡鍜佹晜闁割偆鍟块幏铏圭磽閸屾瑧鍔嶉柨鏇楁櫊閹偞銈i崘鈺冨幈闂佸啿鎼崐缁樻櫠閻㈠憡鐓曢柍瑙勫劤娴滅偓淇婇悙顏勨偓鏍垂閻撳簶鏋栭柡鍥f嚍閸ヮ剚鏅滈柣鎰靛墮閺嬫垿姊洪崫鍕垫Ч妞ゆ垶鐟ヨ灒闁逞屽墴閹? this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: true }); this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: true }); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鍐蹭画闂佹寧娲栭崐鎼佸垂閸岀偞鐓曠憸搴ㄣ€冮崨瀛樺€块柛顭戝亖娴滄粓鏌熸潏鍓хɑ缁绢叀鍩栭妵鍕晜閼测晝鏆ら梺鍝勭焿缁蹭粙鍩ユ径濠庢僵妞ゆ帊鑳堕埀顒勭畺濮婃椽鎮烽弶鎸幮╅梺纭呮珪閿曘垽鎮伴鍢夌喖宕楅悡搴o紡闂備胶鍎甸弲婊呮暜椤忓棛涓嶉柟鎹愵嚙閽冪喖鏌i弮鍌楁嫛闁轰礁瀚伴幃瑙勭瑹椤栨粌甯ュ┑鈥虫▕閸o絽顫忛搹鐟板闁哄洨鍠愰悵鏃堟⒑濞茶骞栭柛濠冩倐椤㈡岸鏁愭径瀣垫濠电偞鍨靛畷顒勫箖閹达附鈷戦柛娑橈梗缁堕亶鏌涘▎蹇涱€楁い顓炴喘楠炲酣鎳為妷銏″? this.emitMediaStateChange(); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼槱閻熸粎澧楃敮鎺楀垂閸岀偞鐓熸俊銈傚亾闁绘锕畷锝堢疀濞戞瑧鍘撻梺鍛婄箓鐎氼參宕宠ぐ鎺撳€垫慨姗嗗墻濡插綊鏌曢崶褍顏鐐村浮瀹曞崬顪冮幆褜妫滄繝鐢靛Х閺佸憡鎱ㄩ銏犵;闁瑰墽绮悡娆戠磽娴i潧鐏╅柡瀣枑閵囧嫰濡烽妷顖濆惈闂佸搫鐬奸崰鏍х暦濠婂嫭濯撮柣鎴炆戦崯鎺楁⒒娴e憡鎯堟俊顐㈤叄瀹曟洟鎳犻浣稿簥濠电偞鍨崹娲吹閹存惊娑㈡偋閸垻鐣靛┑鐐茬墱閸樼晫鎹㈠┑鍡忔灁闁割煈鍠楅悘鈧梻浣告惈閹冲繒绮欓弽顓熷€堕悗锝庡枟閳锋垿姊洪銈呬粶闁兼椿鍨遍弲鍫曨敋閳ь剙螞? this.startActivityDetection(this.state.localStream, { isLocal: true }); - } catch (error) { + } + catch (error) { console.error('Error getting local stream:', error); - // 婵犵數濮烽弫鍛婃叏閻戝鈧倹绂掔€n亞鍔﹀銈嗗坊閸嬫捇鏌涢悢閿嬪仴闁糕斁鍋撳銈嗗坊閸嬫挾绱撳鍜冭含妤犵偛鍟灒閻犲洩灏欑粣鐐烘⒑瑜版帒浜伴柛妯煎帶閳绘捁顦归柟顔筋殜閹兘寮跺▎鍙ョ棯婵犵數濮伴崹娲€﹂崶褜鍤楀┑鐘叉搐闁裤倖淇婇妶鍕槮濞寸媭鍘奸埞鎴︽倷閸欏妫炵紓浣虹帛閸ㄨ儻妫㈤梺闈涚返妫颁胶鐩庨梻浣瑰缁诲倿鎮ч崱娑欏亗闁归偊鍠掗崑鎾诲垂椤愶絿鍑¢柣搴㈠嚬閸樺ジ鈥﹂崶顏嗙杸婵炴垼椴搁弲婵嬫⒑闂堟侗妲归柛鏃€鐗曠叅闁绘梻鍘ч拑鐔兼煏婵炲灝鍔楁俊鎻掔墛娣囧﹪顢涘▎鎺濆妳濠电偛鐗滈崢鍓ф閹惧瓨濯撮柧蹇曟嚀缁楋繝鎮楅崗澶婁壕闂佸綊妫块懗璺虹暤娓氣偓閺岀喖鏌囬敃鈧獮妤€鈹戦姘煎殶缂佽鲸甯掗埥澶婎潨閸℃澹夌紓鍌氬€哥粔鎾晝閵夛妇鈹嶅┑鐘叉祩閺佸啴鏌曢崼婵囧闁哄棭鍙冨娲箹閻愭彃顬嬮梺杞版祰椤曆囨偩閻戣姤鏅查柛顐亝閳诲本绻濆▓鍨灈闁挎洏鍊濋垾锕傛倻婵劏鍋撴担鍓叉建闁逞屽墴楠炲啴鍩℃担鍙夌亖闁诲函绲芥晶搴ㄦ偩閻㈠憡鐓? this.state.session.localUser.mediaState.video = false; this.state.session.localUser.mediaState.audio = false; - - // 闂傚倸鍊搁崐鎼佸磹妞嬪孩顐介柨鐔哄Т绾惧鏌涘☉鍗炲季婵炲皷鏅犻弻鏇熺箾閻愵剚鐝曢梺绋款儏閸婂潡寮诲澶婄厸濞达絽鎲″▓顓㈡⒑閹肩偛濡芥俊顐㈠暣瀵鎮㈤悡搴濈炊闂佸憡娲﹂崢婊堝Χ閸モ晝锛滄繝銏f硾椤牓宕戦姀銈嗙厸鐎光偓鐎n剙鍩岄柧浼欑秮閺屾盯鈥﹂幋婵囩亾闂佸憡锕╅崑濠傤潖濞差亝鐒婚柣鎰蔼鐎氫即鏌涘Ο鐑樻喐缂佽鲸甯¢崺鈧い鎺嶇劍婵潙鈹戦钘夊婵顨嗙换婵嬪閿濆懐鍘梺鍛婃⒐濞叉粎鍒? this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: false }); this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: false }); } } - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢埛姘そ婵¤埖寰勭€n亙妲愰梻渚€娼ц墝闁哄懏鐩幏鎴︽偄鐏忎焦鏂€闂佺粯锚瀵爼骞栭幇鐗堝€垫慨姗嗗墻濡插綊鏌曢崶褍顏鐐村浮瀹曞崬顪冮幆褜妫滄繝鐢靛Х閺佸憡鎱ㄩ銏犵;闁瑰墽绮崐鍨箾閹寸儐浠炬い蹇撶墕閸ㄥ倿鏌¢崘銊у闁绘挻鐟╅弻娑樷攽閸℃浼€闁煎弶鐗滅槐鎾存媴閸撳弶笑闂侀潧鐗婇幃鍌氼嚕婵犳碍鏅查柛娑樺€婚崰鏍х暦瑜版帩鏁婇柟顖嗗啰绱伴梻? async updateLocalMedia(mediaType, value) { await this._updateLocalMediaRefactored(mediaType, value); return; } - async _updateLocalMediaRefactored(mediaType, value) { if (mediaType === 'video' && value) { await this._enableLocalVideo(); this._notifyUserListUpdate(); return; } - this.state.session.localUser.mediaState[mediaType] = value; this._notifyLocalMediaChange(mediaType, value); this.emitMediaStateChange(); - if (mediaType === 'video' && !value) { this._disableLocalVideoTracks(); } - if (mediaType === 'audio') { this._setLocalAudioTrackEnabled(value); } - this._notifyUserListUpdate(); } - async _enableLocalVideo() { try { const newVideoTrack = await this._requestNewVideoTrack(); this._replaceLocalVideoTrack(newVideoTrack); await this._updateOutgoingVideoTrack(newVideoTrack); - this.state.session.localUser.mediaState.video = true; this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream: this.state.localStream }); this._notifyLocalMediaChange('video', true); this.emitMediaStateChange(); this.startActivityDetection(this.state.localStream, { isLocal: true }); - } catch (error) { + } + catch (error) { console.error('Error reopening video:', error); this.state.session.localUser.mediaState.video = false; this._notifyLocalMediaChange('video', false); } } - async _requestNewVideoTrack() { const newVideoStream = await navigator.mediaDevices.getUserMedia(VIDEO_ONLY_CONSTRAINT); const newVideoTrack = newVideoStream.getVideoTracks()[0]; - if (!newVideoTrack) { throw new Error('Failed to get video track'); } - return newVideoTrack; } - _replaceLocalVideoTrack(newVideoTrack) { if (this.state.localStream) { const oldVideoTracks = this.state.localStream.getVideoTracks(); @@ -232,17 +156,13 @@ class CallStateManager { this.state.localStream.addTrack(newVideoTrack); return; } - this.state.localStream = new MediaStream([newVideoTrack]); } - async _updateOutgoingVideoTrack(newVideoTrack) { if (!this.renderstreaming) { return; } - console.log('Updating video track in WebRTC connection'); - if (this.role === 'host') { const participantIds = Object.keys(this.state.remoteStreams); for (const participantId of participantIds) { @@ -250,29 +170,22 @@ class CallStateManager { } return; } - await this._updateVideoTrackForPeer(newVideoTrack); } - async _updateVideoTrackForPeer(newVideoTrack, participantId = undefined) { const transceivers = this.renderstreaming.getTransceivers(participantId); if (!transceivers) { return; } - - const videoTransceivers = transceivers.filter(transceiver => - transceiver.sender && transceiver.sender.track && transceiver.sender.track.kind === 'video' - ); - + const videoTransceivers = transceivers.filter(transceiver => transceiver.sender && transceiver.sender.track && transceiver.sender.track.kind === 'video'); if (videoTransceivers.length > 0) { await this._replaceVideoTrackOnTransceivers(videoTransceivers, newVideoTrack, participantId); - } else { + } + else { this._addVideoTransceiver(newVideoTrack, participantId); } - this._scheduleVideoSenderUpdate(participantId); } - async _replaceVideoTrackOnTransceivers(videoTransceivers, newVideoTrack, participantId) { for (const transceiver of videoTransceivers) { try { @@ -280,17 +193,14 @@ class CallStateManager { console.log(participantId ? `Replaced video track for participant ${participantId}` : 'Successfully replaced video track'); - } catch (error) { - console.error( - participantId - ? `Error replacing video track for ${participantId}:` - : 'Error replacing video track:', - error - ); + } + catch (error) { + console.error(participantId + ? `Error replacing video track for ${participantId}:` + : 'Error replacing video track:', error); } } } - _addVideoTransceiver(newVideoTrack, participantId) { try { if (participantId) { @@ -298,29 +208,23 @@ class CallStateManager { console.log(`Added new video transceiver for participant ${participantId}`); return; } - this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' }); console.log('Added new video transceiver'); - } catch (error) { - console.error( - participantId - ? `Error adding video transceiver for ${participantId}:` - : 'Error adding video transceiver:', - error - ); + } + catch (error) { + console.error(participantId + ? `Error adding video transceiver for ${participantId}:` + : 'Error adding video transceiver:', error); } } - _scheduleVideoSenderUpdate(participantId) { setTimeout(() => { this.setCodecPreferences(participantId); }, 100); setTimeout(() => { this.setVideoEncodingParameters(participantId); }, 200); } - _disableLocalVideoTracks() { if (!this.state.localStream) { return; } - this.state.session.localUser.mediaState.video = false; this.state.localStream.getTracks().forEach(track => { if (track.kind === 'video') { @@ -328,12 +232,10 @@ class CallStateManager { } }); } - _setLocalAudioTrackEnabled(value) { if (!this.state.localStream) { return; } - this.state.session.localUser.mediaState.audio = value; this.state.localStream.getTracks().forEach(track => { if (track.kind === 'audio') { @@ -341,11 +243,9 @@ class CallStateManager { } }); } - _notifyLocalMediaChange(mediaType, value) { this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType, value }); } - _notifyUserListUpdate() { this.notify({ type: 'USER_LIST_UPDATE', @@ -353,93 +253,71 @@ class CallStateManager { remoteUser: this.state.session.remoteUser }); } - onSocketEvent(eventName, handler) { this.socketEventHandlers[eventName] = handler; } async connectSignaling() { await this.setupConfig(); - const { signaling, reused } = await ensureSignalingStarted(this._signaling, this.useWebSocket); this._signaling = signaling; - this._inviteEventSignaling = bindInviteSocketEvents( - this._signaling, - this.socketEventHandlers, - this._inviteEventSignaling - ); - + this._inviteEventSignaling = bindInviteSocketEvents(this._signaling, this.socketEventHandlers, this._inviteEventSignaling); if (reused) { console.log('Signaling already connected, reusing existing instance'); return this._signaling; } - console.log('Signaling connected (WebSocket only, no room yet)'); return this._signaling; } - getActiveSignaling() { return getActiveSignalingInstance(this._signaling, this.renderstreaming); } - sendInviteCall(payload) { sendInviteSignal(this.getActiveSignaling(), 'sendInviteCall', payload); } - sendInviteAccepted(payload) { sendInviteSignal(this.getActiveSignaling(), 'sendInviteAccepted', payload); } - sendInviteRejected(payload) { sendInviteSignal(this.getActiveSignaling(), 'sendInviteRejected', payload); } - syncSocketUserInfo(userInfo = null) { const payload = buildSocketUserInfoPayload(userInfo, this.state.session.localUser); - this.state.session.localUser = { ...this.state.session.localUser, id: payload.id, name: payload.name, avatar: payload.avatar }; - sendSocketUserInfo(this.getActiveSignaling(), payload); } - async _createSignalingAndRTC(connectionId) { this.connectionId = connectionId; this.state.session.status = 'connecting'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); - if (!this.state.localStream) { console.log('Local stream not available, waiting for initialization...'); await new Promise((resolve) => { const checkStream = () => { if (this.state.localStream) { resolve(); - } else { + } + else { setTimeout(checkStream, 100); } }; checkStream(); }); } - const signaling = this._signaling || createSignalingInstance(this.useWebSocket); const config = getRTCConfiguration(); this.renderstreaming = new RenderStreaming(signaling, config); this._signaling = null; } - async setUp(connectionId) { await this._createSignalingAndRTC(connectionId); this._registerCallbacks(); await this._startConnection(connectionId); } - - /** - * 濠电姷鏁告慨鐑藉极閹间礁纾绘繛鎴旀嚍閸ヮ剦鏁囬柕蹇曞Х椤︻噣鎮楅崗澶婁壕闂佸憡娲﹂崑澶愬春閻愬绠鹃悗鐢殿焾瀛濆銈嗗灥閹虫劗鍒掓繝姘ㄩ柍鍝勫€婚崢鎾绘偡濠婂嫮鐭掔€规洘绮撻幃銏$附婢跺﹥顓块梻浣稿閻撳牓宕戦崱娆戜笉婵炲棙鍔楃粻鍓р偓鍏夊亾闁逞屽墴閸┾偓妞ゆ巻鍋撴い顭戞C闂傚倸鍊搁崐鐑芥倿閿曞倸绠栭柛顐f礀绾惧潡鏌熼幆鐗堫棄缁炬儳顭烽弻锝呂熼懖鈺佺闂? - */ _registerCallbacks() { this.renderstreaming.onNewPeer = (participantId) => { console.log(`New peer created for ${participantId}, adding local tracks`); @@ -452,25 +330,17 @@ class CallStateManager { this.setVideoEncodingParameters(participantId); } }; - - // 闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺鍝勬缁捇寮婚埄鍐ㄧ窞閻庯綆浜為崝鎼佹⒑閹肩偛鈧洟鎮ч幘璇茶摕鐟滄垹绮诲☉銏犵缁炬儳顑囬、鍛存煟閻斿摜鐭嬫繝銏★耿閹矂宕掗悙鑼舵憰濠电偞鍨崹鐟版暜闁荤喐绮岀粔鐢电矉閹烘鐓涢柛娑卞枤閸? this.renderstreaming.onConnect = (connectionId, data) => { - // 婵犵數濮烽弫鎼佸磿閹寸姴绶ら柦妯侯棦濞差亝鏅滈柣鎰靛墮鎼村﹪姊虹粙璺ㄧ伇闁稿鍋ゅ畷鎴﹀Χ婢跺鍘繝鐢靛€崘顭戜患闂佸搫顑呴崐鍨潖濞差亜鎹舵い鎾跺仜婵″搫鈹戦悙鎻掔骇闁绘濮惧Λ銏ゆ椤愩垺澶勯柟鏋€栭幆鏃堝Ω閵壯冣偓鐐烘⒑閹稿海绠撻柟鍐查叄閸╂稒寰勭€n剛顔曢柣搴f暩椤牏鏁崼鏇熺厵闁惧浚鍋撻懓璺ㄢ偓娈垮櫘閸嬪嫰顢橀崗鐓庣窞濠电姴娲﹂弳鍛存⒒娴e鈧偓闁稿鎹囬弻鐔碱敇閻樿尙浜?participant闂? if (data && data.role) { this.role = data.role; - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢埛姘そ婵¤埖寰勭€n亙妲愰梻渚€娼ц墝闁哄懏鐩幏鎴︽偄鐏忎焦鏂€闂佺偨鍎抽崑銊╁焵椤掆偓閻涙瓫lUser闂傚倸鍊搁崐鐑芥倿閿曞倹鍎戠憸鐗堝笒缁€澶屸偓鍏夊亾闁告洦鍋呭Σ顒勬⒑闂堚晝绋婚柛妯兼畮st闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢妶鍥╃厠闂佸搫顦伴崵姘洪鍕幯囨煕閵夛絽濡奸幖? this.state.session.localUser.isHost = (this.role === 'host'); - // 婵犵數濮烽弫鎼佸磿閹寸姴绶ら柦妯侯棦濞差亝鏅滈柣鎰靛墮鎼村﹪姊虹粙璺ㄧ伇闁稿鍋ゅ畷鎴﹀Χ婢跺鍘繝鐢靛仧閸嬫挸鈻嶉崱娑欑厱闁靛牆鎷戦弨濠氭煏閸パ冾伂缂佺姵鐩獮姗€寮堕幋鐐典悍闂傚倷鑳剁划顖炲箰閹绢喖鐤鹃柣妯款嚙閽冪喖鏌ㄥ┑鍡╂Ц閹喖姊洪棃娑辨Ф闁稿孩濞婂畷锝夊磽椤$icipantId闂傚倸鍊搁崐鐑芥倿閿旈敮鍋撶粭娑樻噽閻瑩鏌熸潏楣冩闁稿孩鏌ㄩ埞鎴﹀磼濠婂海鍔搁梺鍝勵儎缁舵岸寮婚弴銏犻唶婵犻潧娴傚Λ銈咁渻閵堝倹娅嗛柣鎿勭節閻涱噣寮介鐔蜂壕婵炴垶鐟悞鐣岀磼閻樼鑰块柡宀嬬秮椤㈡顦虫い顐ゅ壃rticipants-sync婵犵數濮烽弫鎼佸磻閻愬搫鍨傞柛顐f礀缁犲綊鏌嶉崫鍕櫣闁活厽顨婇弻宥堫檨闁告挻绋撳Σ鎰板箳閺冣偓鐎氭岸鏌熺紒妯虹瑲婵炲牜鍘奸—鍐Χ鎼粹€崇闂佸憡姊归崹鑸电┍婵犲洤閱囬柡鍥╁仱閸炶泛鈹戦悩缁樻锭婵炴潙鍊垮畷顒勫醇閺囩啿鎷? if (data.participantId) { this.selfParticipantId = data.participantId; } console.log(`Connected as ${this.role}, participantId: ${this.selfParticipantId}`); } - // 闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺鍝勬缁捇寮婚埄鍐ㄧ窞閻庯綆浜為崝鎼佹⒑閹肩偛鈧洟鎮ч幘璇茶摕鐟滄垹绮诲☉銏犵缁炬儳顑囬、鍛存煟閻斿摜鐭嬫繝銏★耿閹矂宕掗悙鑼舵憰濠电偞鍨堕敃鈺侇焽閳哄倶浜滈柟鍝勬娴滈箖姊洪幖鐐测偓娑欑椤掑倹顫曢柟鐑樻尰缂嶅洭鏌曟繛褍鎳愰弶浠嬫煟鎼淬埄鍟忛柛鐘崇墵椤㈡岸顢橀姀鐘靛姦濡炪倖甯婇懗鍫曞煀閺囥垺瀚呴梺顒€绉甸悡鐔兼煙缂併垹鐏犲ù婊堢畺濮婂宕掑▎鎺戝帯缂備緡鍣崹鍫曞灳閿曞倸閱囬柕澶堝劜濡差剟姊洪崫鍕枆闁告ê銈搁幃鐐哄垂椤愮姳绨婚梺鐟邦嚟閸嬫盯顢氬鍕╀簻闁挎梹鍎抽崢鎾煙椤旇偐绉烘鐐叉喘椤㈡瑩鎮¢獮顒夋磮going this.state.session.status = 'ongoing'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' }); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鍐蹭画闂侀潧顦弲娑氬閻熼偊娓婚悗锝庝簼閹癸絿绱掗悩铏拻闁逞屽墮閸樻粓宕戦幘缁樼厱闁哄洢鍔屾晶顔界箾閸繄鐒告慨濠冩そ瀹曘劍绻濇担铏圭畳闂備礁鎲¢敋闁靛牊鎮傞弫鎰版倷閻戞ɑ娅滄繝銏f硾椤戝洭宕㈤柆宥嗩棅妞ゆ劑鍨洪幖鎰亜閹存繃鍤囩€规洘绻傞…銊╁礋椤忓棛鐣鹃梻浣虹帛閸旀牞銇愰崘顔肩劦妞ゆ巻鍋撶紓宥咃躬閸ㄩ箖寮崼婵堝姦濡炪倖甯婂鎺旀崲閸℃ǜ浜滈柡宥冨妺缁堕亶鏌涙惔娑樺姦闁哄本绋戦~婵嬵敆娴e憡鐏庢繝娈垮枛閿曘倝鈥﹀畡鎵殾濠靛倸鎲¢崑鍕煟閹寸伝顏勨枍濮樿埖鈷掑ù锝堟鐢盯鏌熼崨濠冨€愰柟顔哄劜缁虹晫绮欓幐搴b偓顒勬倵楠炲灝鍔氶柟宄邦儏椤洭鍩℃担鍕剁悼娴狅箓宕滆椤囨⒑濞茶骞楁い銊ワ工椤繘宕崝鍊熸缁辨帒螣鐞涒剝鐎奸梻鍌欑閹测€愁潖瑜版帒鍨傞柛褎顨呯粻鏍煥閻斿搫校闁搞倕顑囬惀顏堝级濞嗙偓楔闂佹椿鍘介〃濠傤潖閾忚鍏滈柛娑卞枛濞懷呯磽娴h棄鐨洪柡鍜佸亞濡叉劙骞樼拠鑼啋缂傚倷鐒﹁彜闁瑰嘲鎼—鍐Χ閸℃鐟愰梺缁樺釜婵″洦绂嶉幖渚囨晣闁靛骏绱曢崢楣冩⒑閻撳寒娼熼柛濠冩礋瀵鈽夊锝呬壕婵炲牆鐏濋弸娑欍亜閺囥劌寮柛鈹惧亾濡炪倖甯婇懗鍫曞煀閺囥垺瀚呴梺顒€绉甸悡鐔兼煙缂併垹鐏犲ù婊堢畺濮婂宕掑▎鎺戝帯缂備緡鍣崹鍫曞灳閿曞倸閱囬柕澶堝劜濡差剟姊洪崫鍕枆闁告ê銈搁幃? if (this.role === 'participant') { if (this.state.localStream) { this.state.localStream.getAudioTracks().forEach(track => { @@ -481,37 +351,24 @@ class CallStateManager { this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: false }); console.log('Participant joined with audio muted by default'); } - - // 闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺鍝勬缁捇寮婚埄鍐ㄧ窞閻庯綆浜為崝鎼佹⒑閹肩偛鈧洟鎮ч幘璇茶摕鐟滄垹绮诲☉銏犵缁炬儳顑囬、鍛存煟閻斿摜鐭嬫繝銏★耿閹矂宕掗悙鑼舵憰濠电偞鍨堕敃鈺侇焽閳哄倶浜滈柟鐑樺灥椤忔挳鏌嶈閸撴岸銆冮崼銉ョ疅闁归棿绀佺痪褔鏌涢…鎴濇灀闁稿鎹囧畷绋课旈埀顒勬嫅閻斿吋鐓曟繛鎴濆船閺嬬喎鈹戦娑欏唉闁诡喖鍢查埢搴ょ疀閹垮啩鐥梺璇插绾板秵绻涙繝鍌ゆ綎婵炲樊浜濋ˉ鍫ユ煃閸濆嫭鍣烘い搴$У缁绘繈濮€閵忊€虫畬濠碘槅鍋呯换鍌烆敋閿濆鏁冮柨鏇楀亾閸ュ瓨绻濋姀锝嗙【闁挎洩绠撻、妯好洪鍛嫼闂佺粯鍔﹂崜婵囩珶濮椻偓閺屾稒绻濋崶鑸殿棖闂佺懓绠嶉崹钘夌暦閸楃偐妲堟繛鍡樺灥楠? this.sendMessage('user-info', { id: this.state.session.localUser.id, name: this.state.session.localUser.name, avatar: this.state.session.localUser.avatar }); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鍐蹭画闂佹寧娲栭崐鎼佸垂閸岀偞鐓曠憸搴ㄣ€冮崨瀛樺€块柛顭戝亖娴滄粓鏌熸潏鍓хɑ缁绢叀鍩栭妵鍕晜閻撳寒娲紓浣介哺閹告悂顢樻總绋挎そ濞达絿顭堟竟搴ㄦ煟鎼淬値娼愭繛鍙夘焽閸掓帒鐣濋崟鍨櫍婵犻潧鍊婚…鍫濐啅濠靛洢浜滈柡宥冨妽閻ㄦ垿鏌$仦鐣屽闁宠鍨块幃娆撴嚋闂堟稒閿紓鍌欑劍閻擄紕绮婚弽顓犲祦闁规壆澧楅崐閿嬨亜閹哄秶顦︾€殿喖娼″楦裤亹閹烘搫绱电紓浣插亾濞达綀娅f稉宥夋煙鐎涙缂氶柍褜鍓氱敮鎺楋綖濠靛鏁嗗┑鐘插€搁~鐘电磽閸屾瑨顔夐柛瀣尭椤法鎹勯搹鍦紘濠碘槅鍋掗崹閬嶅Φ閸曨垰绠绘俊銈傚亾闁哥噥鍨堕幃姗€宕卞▎鎴狅紳婵炶揪缍€濞咃絿鏁☉姘辨/闁哄娉曞瓭濡炪値鍘煎ú銊╁箯閻樺樊鍟呮い鏃€鍎冲鎶芥⒒娴e憡鍟炵紒顔肩墕椤啴骞掗弮鈧~鏇㈡煙閸撗呭笡闁抽攱鍨块幃璺衡槈閹哄棗浜鹃柛蹇撴噹椤ユ岸姊绘担钘夊惞濠殿喗鎸抽獮鏍敃閿曗偓缁犺銇勯幇鍫曟闁稿鍊块弻娑氫沪閸撗€濮囬梺鍛婄懃缁绘垹鎹㈠┑瀣瀭妞ゆ劧绲介弳妤呮⒑鐠団€虫灈闁靛牊鎮傚璇差吋婢跺á銊╂煥閺冨洤顥嶉柍褜鍓欏锟犲蓟閻旂⒈鏁嶆慨姗嗗墯濞堝鎮楃憴鍕闁哥姵鐗犻妴渚€寮撮姀鐙€娼婂銈庡亽閸樿棄螣婵犲啰绡€闁汇垽娼ф牎濡炪倖姊归悧鐘茬暦娴兼潙鍐€妞ゆ挆鍕珗闂備焦鎮堕崕顖炲礉瀹ュ鐭楅煫鍥ㄧ⊕閻撴瑩姊婚崒姘煎殶妞わ讣濡囩槐? this.emitMediaStateChange(); - if (this.state.localStream) { - // const tracks = this.state.localStream.getTracks(); - // for (const track of tracks) { - // this.renderstreaming.addTransceiver(track, { direction: 'sendonly' }); - // } - // this.setCodecPreferences(); this.showStatsMessage(); - } else { + } + else { console.error('Local stream is not available'); - showNotification('Local video stream is not available', 'error'); + showNotification('本地视频流不可用', 'error'); } }; - - // 闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺鍝勬缁捇寮婚悢铏圭<闁靛繒濮甸悘鍫濃攽閳藉棗浜濋拑閬嶆煏閸パ冾伃妤犵偞顭囬幑鍕姜閻楀牆鐓曠紓鍌欒兌閸嬫捇宕曢幎瑙b偓锕傛倻閽樺鎽曞┑鐐村灟閸ㄧ懓鏁柣鐔哥矊缁夌數绮嬮幒妤€鐓涢柛娑卞枤閸樹粙姊洪棃娴ゆ盯宕橀埡鍐ч偗闂傚倷娴囧銊х矆娴h櫣鐭撻柣鐔稿閺嗭箑鈹戦崒姘暈闁稿瀚伴弻娑樷槈濮楀牊鏁鹃梺浼欓檮閸ㄥ灝顫忕紒妯诲闁告稑锕ら弳鍫ユ煟閵忊晛鐏i柛瀣仱瀵尙鎹勬担鏇熸閸┾偓妞ゆ帒瀚弲婵嬫煏韫囧鈧洟鎮欐繝鍐︿簻闁瑰搫妫楁禍鍓х磽娴f彃浜炬繝銏e煐閸旀牠鎮¢崘顔界厪濠㈣泛鐗嗘俊浠嬫煟濠垫劒閭柡宀嬬到閳规垿宕熼鐔蜂壕婵犻潧顑呴拑?disconnect 濠电姷鏁告慨鐑藉极閹间礁纾婚柣鎰惈閸ㄥ倿鏌i姀鐘冲暈闁稿顑呴埞鎴︽偐閹绘帗娈銈嗘礋娴滃爼寮诲☉妯锋婵☆垰鍚嬮幉濂告⒑閸濆嫭濯奸柛鎾寸洴閸┾偓妞ゆ巻鍋撻柛妯荤矒瀹曟垿骞樼紒妯煎幈闂佸搫娲﹂敋濠碉紕鍘ч湁婵犲﹤鎳庢禍楣冩煙娓氬灝濡兼い顐g矒瀹曞崬螖閳ь剟锝為锔解拺缂侇垱娲樺▍鍡樸亜閵娿儻韬€?host 缂傚倸鍊搁崐鎼佸磹妞嬪海鐭嗗〒姘e亾鐎规洘鍔栫粋鎺斺偓锝呯仛閺咁亪鎮峰鍐閸楅亶鏌熼悧鍫熺凡缂佺媴缍侀弻锝咁潨閸℃ぞ绨绘繛瀛樼矋閸庢娊婀侀梺鎸庣箓閹冲酣寮冲▎鎰╀簻闁靛鍎虫晥闂佸搫鏈惄顖涗繆閻㈢纭€闁绘劕鐡ㄩ幃娆戠磽娴e搫顎撶紓宥勭窔瀵鈽夊搴⑿俊鐐€戦崝灞轿涘┑瀣瀬鐎广儱顦伴崑鍕煕韫囨艾浜归柛姗€浜跺Λ鍛搭敃閵忊€愁槱闂佸搫琚崝鎴﹀春閵忋倕鍗抽柣鏂垮缁犳艾顪冮妶鍡楀Ё缂佹彃澧芥禍鎼佹晝閸屾稓鍘? this.renderstreaming.onDisconnect = () => { console.log('Received disconnect from server, host left or room closed'); - this.hangUp(); // 闂傚倸鍊搁崐宄懊归崶顒婄稏濠㈣泛顭悞鑺ョ箾閸℃绂嬮柣鎺嶇矙閺屽秹濡烽敂鍓х嵁濠电偞鍨崹褰掑垂閸屾稏浜滈柡鍥╁仦閸e綊鏌¢崨顖氣枅婵﹥妞藉畷鐑筋敇閻樿尙顐奸梺姹囧焺閸ㄨ京鏁悢鐓幬﹂柟鎵閸嬫劗绱撴担楠ㄦ岸骞忔繝姘拺缂佸鍎婚~锕傛煕婵犲啯绀€妞ゆ洩绲块幑鍕Ω瑜忛敍婊堟煛婢跺﹦澧戦柛鏂挎捣缁鎮欓幖顓燁啍闂佺粯鍔栧娆愭叏瀹ュ洠鍋撶憴鍕閻㈩垱甯¢崺銉﹀緞婵炵偓鐎婚梺鐟扮摠缁诲倹顨欓梻鍌氬€风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺? + this.hangUp(); }; - - // SDP Answer 闂傚倸鍊搁崐宄懊归崶顒婄稏濠㈣泛顑囬々鎻捗归悩宸剰缁炬儳娼¢弻锛勪沪鐠囨彃濮庨梺鍝勵儎閼冲爼骞夐幖浣瑰亱闁割偅绻勯悷銊╂⒑缂佹ê濮囬柨鏇ㄤ邯瀵鏁愭径濠勭杸濡炪倖宸婚崑鎾淬亜閿濆牊鐝紒缁樼洴瀹曠厧鈹戦崼婵堝幗闂備礁鎼悮顐﹀礉瀹€鍕叀濠㈣泛艌閺嬪酣鐓崶銊﹀鞍闁硅櫕鐟╁缁樼瑹閳ь剙顭囬懡銈呯筏濡わ絽鍟粻鏍煕鐏炴儳鍤柛銈嗘礃閵囧嫰骞掗幋婵愪患缂備胶濯崹鎶藉焵椤掆偓閸樻粓宕戦幘鏂ユ斀闁绘ɑ褰冮埀顒傛嚀閳诲秴螖閸愵亞锛濋梺绋挎湰閼归箖鍩€椤掍焦鍊愮€规洘婢橀~婵堟崉閾忓湱宕舵繝娈垮枟椤牊銇旈幖渚婄稏闁哄洨鍠嗘禍婊堢叓閸ャ劍灏靛褎鐩弻娑欐償閳锋凹浜﹢渚€姊洪幐搴g畵婵炴潙鍊块幃鐐烘倷椤戣法绠氬銈嗗姧缂嶅洭鎳撻崸妤佺厸閻忕偟顭堟晶鑼磼濡ゅ啫鏋涢柛鈹惧亾濡炪倖甯掔€氥劑鍩€椤掆偓閸婂潡寮崒鐐村仼鐎光偓婵犲啴鏁滈梻鍌欑濠€閬嶅磿閵堝绠板Δ锝呭暙閻撴鈧箍鍎卞Λ娑氬姬閳ь剟姊婚崒姘卞濞撴碍顨婂畷鏇㈠箛椤撴粈绨婚梺鍝勫€圭€笛囧窗濮椻偓閺屸€崇暆鐎n剛袦婵犳鍠掗崑鎾绘⒑閺傘儲娅呴柛鐘宠壘椤洭鏁撻悩鍐测偓? this.renderstreaming.onGotAnswer = (connectionId) => { console.log('SDP Answer received, resetting encoding parameters for connectionId:', connectionId); if (this.role === 'host') { @@ -519,25 +376,22 @@ class CallStateManager { for (const pid of allParticipantIds) { setTimeout(() => { this.setVideoEncodingParameters(pid); }, 50); } - } else { + } + else { setTimeout(() => { this.setVideoEncodingParameters(); }, 50); } }; - this.renderstreaming.onParticipantJoined = (participantId) => { console.log(`Participant joined: ${participantId}`); this._upsertParticipant(participantId); this._notifyParticipantsUpdate(); this.broadcastParticipantsList(); }; - - // participant缂傚倸鍊搁崐鎼佸磹妞嬪海鐭嗗〒姘e亾鐎规洘鍔栫粋鎺斺偓锝呯仛閺咁亪鎮峰鍐閸楅亶鏌熼悧鍫熺凡缂佺媴缍侀弻銊╁即濡も偓娴滈箖姊虹紒妯哄闁挎洦浜濠氭晲婢跺﹦鐤€濡炪倖宸婚崑鎾淬亜閿濆牊鐝紒缁樼洴瀹曠厧鈹戦崼婵堝幗闂備礁鎼悮顐﹀礉瀹€鍕叀濠㈣泛谩閻斿吋鍤掗柕鍫濇礌閺岊洉st闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁秆勵殔閽冪喖鏌曟繛鍨姉婵℃彃鐗婃穱濠囶敍濠婂懎绗¢梺鍦厴娴滆泛顫忛搹鐟板闁哄洨鍠愬▓顒勬⒑閹肩偛鍔橀柛鏂款樀楠炲繘鎳¢妶鍥╋紳闂佺鏈銊︽櫠閵忕姭鏀芥い鏃€鍎抽崢鎾煙椤栨俺瀚伴柍瑙勫灩閳ь剨缍嗛崑鍡涘储娴犲鈷戦柛锔诲幖閸斿绻涢崣澶樼劷婵炴垵鐏氶妶锝夊礃閳哄啫骞楅梺鍦劋婵炲﹤鐣峰┑瀣婵犻潧鐗婂▓楣冩⒑缂佹ê鐏辩悮娆撴煃瑜滈崜銊х不閹捐崵宓侀柟鐑橆殔缁秹鏌涚仦璇测偓鏍х摥 this.renderstreaming.onParticipantLeft = (participantId) => { console.log(`Participant left: ${participantId}, room still active`); this.updateRemoteUserStatus('offline'); this.updateRemoteUserNetworkQuality('no_signal'); - showNotification('The other participant left the call', 'warning'); - // 濠电姷鏁告慨鐑藉极閹间礁纾婚柣鎰惈缁犱即鏌熼梻瀵割槮缂佺姷濞€閺岀喖鎮ч崼鐔哄嚒缂備胶濮甸悧鏇㈠煘閹达附鍋愰柟缁樺俯娴犻箖鏌?participant 闂傚倸鍊搁崐鐑芥倿閿曞倹鍎戠憸鐗堝笒缁€澶屸偓鍏夊亾闁逞屽墴閸┾偓妞ゆ帊绀侀崵顒勬煕閵婏箑鈻曟鐐村灴婵偓闁绘﹩鍋呴悗濠氭⒑閻熼偊鍤熷┑顔芥綑閻g兘濡烽妷顔藉瘜闂侀潧鐗嗗Λ娑欐櫠椤掑倻纾奸悗锝庡亝瀹曞苯鈹? + showNotification('对方已离开通话', 'warning'); if (this.state.remoteStreams[participantId]) { this.state.remoteStreams[participantId].getTracks().forEach(track => track.stop()); delete this.state.remoteStreams[participantId]; @@ -547,81 +401,46 @@ class CallStateManager { this.state.remoteStream = null; } this._removeParticipant(participantId); - // 闂傚倸鍊搁崐鎼佸磹妞嬪孩顐介柨鐔哄Т绾惧鏌涘☉鍗炲季婵炲皷鏅犻弻鏇熺箾閻愵剚鐝曢梺绋款儏閸婂綊濡甸崟顖氱労闁告劏鏅滅欢婊堟⒒閸屾艾鈧绮堟笟鈧獮鏍敃閳锋碍妞芥俊鑸靛緞鐎n亙妲愰梻渚€娼ц墝闁哄懏鐩幏鎴︽偄鐏忎焦鏂€闂佺粯锚瀵埖寰勯崟顖涚厸闁告粈绀佹晶浼存煃瑜滈崜娆撳储濠婂牆纾婚柟鍓х帛閻撳啰鎲稿鍫濈闁绘柨顨庨崵鏇熴亜閹板洩顕уú顓烆嚕閻㈠灚顫曢柛宥囧交cipantId婵犵數濮烽弫鎼佸磻閻樿绠垫い蹇撴缁躲倝鏌涢幘妤€鍟悘濠囨⒑閸撴彃浜栭柛銊ㄦ閳ь剚鑹鹃…鐑藉蓟閿濆绠抽柟瀵稿Х瀹曠垔nectionId婵犵數濮烽弫鎼佸磻閻斿澶愬箛閺夎法锛涢梺褰掑亰閸樹粙宕h箛鎿冩富閻庯綆浜妤呮煕閵婏妇绠為柡宀嬬秮婵℃悂濡烽埗鈺佷壕妞も晜鏀rer this.notify({ type: 'PARTICIPANT_LEFT', connectionId: participantId }); this._notifyParticipantsUpdate(); this.broadcastParticipantsList(); }; - - // 闂傚倸鍊风粈渚€骞栭位鍥焼瀹ュ懐锛涢梺纭呮彧缁犳垹绮堟径鎰厵閻庣數顭堟禒褎銇勯埡鍌滃弨闁哄矉缍侀獮鍥濞戞﹩娼炬俊鐐€愰弲婊堟偂閿熺姴绠栨俊銈呮噺閺呮煡鏌涚仦鍓р姇闁绘繃娲栭—鍐Χ閸℃顫堢紓渚囧枟閻熲晛顕g拠娴嬫闁靛繒濮堥妸褎鍠愮€广儱妫涢々鐑芥煃閸濆嫭鍣洪柣? this.renderstreaming.onTrackEvent = (data) => { this._handleTrackEvent(data); }; - this.renderstreaming.onMessage = (data) => { this._handleRenderStreamingMessage(data); }; } - - /** - * 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼槱閻熸粎澧楃敮鎺楀垂閸岀偞鐓熸俊銈傚亾闁绘锕畷锝堢疀濞戞瑧鍙嗛梺缁樻磻閻掞箓锝為幁鍢C闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺鍝勬缁捇寮婚悢铏圭<闁靛繒濮甸悘鍫濃攽閿涘嫬浠掔紒顔界懇楠炲啫顫滈埀顒勫箖濞嗘劖濯撮柛鎰ㄦ櫓閳ь剚顨呴埞鎴︻敊绾嘲濮涢梺绋款儐閹搁箖骞? - * @async - * @param {string} connectionId - 闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺鍝勬缁酣鎯€椤忓牊鍋嬮柛顐ゅ枑閸? - * @returns {Promise} - */ async _startConnection(connectionId) { - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼槱閻熸粎澧楃敮鎺楀垂閸岀偞鐓熸俊銈傚亾闁绘锕畷锝堢疀濞戞瑧鍙嗛梺缁樻磻閻掞箓锝為幁鍢C闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺? await this.renderstreaming.start(); await this.renderstreaming.createConnection(connectionId); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼槱閻熸粎澧楃敮鎺楀垂閸岀偞鐓熸俊銈傚亾闁绘锕畷锝堢疀濞戞瑧鍘撻梺鍛婄箓鐎氼剟寮抽悢铏规/濡わ絽鍟伴悾娲煛瀹€鈧崰鎾诲焵椤掑倹鏆╂い顓炵墕閺嗏晠姊绘担渚敯妞ゎ偄顦叅闁绘梻鍘х粻鏍喐閻楀牆绗掔紒鐘崇洴閺屽秵娼幍顕呮М濡炪値鍋勫ú顓烆潖濞差亝顥堥柍鍝勫暙閸╁矂姊洪崷顓涙嫛闁稿顦甸幃妯尖偓锝庡枟閳锋垿姊洪銈呬粶闁兼椿鍨遍弲鍫曨敋閳ь剙螞? this.startNetworkQualityDetection(); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼槱閻熸粎澧楃敮鎺楀垂閸岀偞鐓熸俊銈傚亾闁绘锕畷锝堢疀濞戞瑧鍘撻梺鍛婄箓鐎氼參宕宠ぐ鎺撳€垫慨姗嗗墻濡插綊鏌曢崶褍顏鐐村浮瀹曞崬顪冮幆褜妫滄繝鐢靛Х閺佸憡鎱ㄩ銏犵;闁瑰墽绮悡娆戠磽娴i潧鐏╅柡瀣枑閵囧嫰濡烽妷顖濆惈闂佸搫鐬奸崰鏍х暦濠婂嫭濯撮柣鎴炆戦崯鎺楁⒒娴e憡鎯堟俊顐㈤叄瀹曟洟鎳犻浣稿簥濠电偞鍨崹娲吹閹存惊娑㈡偋閸垻鐣靛┑鐐茬墱閸樼晫鎹㈠┑鍡忔灁闁割煈鍠楅悘鈧梻浣告惈閹冲繒绮欓弽顓熷€堕悗锝庡枟閳锋垿姊洪銈呬粶闁兼椿鍨遍弲鍫曨敋閳ь剙螞? this.startActivityDetection(this.state.localStream, { isLocal: true }); - //闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼槱閻熸粎澧楃敮鎺楀垂閸岀偞鐓熸俊銈傚亾闁绘锕畷锝堢疀濞戞瑧鍘撻梺鍛婄箓鐎氼剟鍩€椤掑喚娼愮紒鍌氱Ч婵″爼宕卞▎鎴犳濠电姷鏁告慨鎾磹鐟欏嫬鍨旈柣鎾崇瘍瑜版帗鍋傞幖绮规閸嬫捇宕归鍛稁缂傚倷鐒﹂…鍥╁姬閳ь剟姊洪崨濠傚闁告柨顦靛绋库枎韫囷絾瀵岄梺闈涚墕缁绘帞绮旈浣虹濠㈣泛顑囬妴鎺楁煕濞嗗繑鍤囬柡宀嬬秮閹晠宕f径濠庢П闁荤喐绮嶅姗€宕幎閾扮兘宕熼鍌滅槇缂佸墽澧楄摫妞ゎ偄锕弻娑氣偓锛卞啩澹曢梻鍌欒兌椤牓顢栭崱娑樼闁煎鍊栧畷? this.startActivityDetection(this.state.remoteStream, { isLocal: false }); } - - /** - * 闂傚倸鍊搁崐椋庣矆娴i潻鑰块梺顒€绉埀顒婄畵瀹曞ジ濡烽妷褝绱柣鐔哥矌婢ф鏁幒妤佸珔闁绘柨鍚嬮悡鏇熴亜椤愵偄鍘撮柛瀣崌瀹曨偆绮垫總顤夐梻鍌氬€风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺? - * Host闂傚倸鍊搁崐椋庣矆娴i潻鑰块梺顒€绉埀顒婄畵瀹曞ジ濡烽妷褝绱柣鐔哥矌婢ф鏁幒妤佸珔闁绘柨鍚嬮悡鏇㈡煏婢舵ê鏋涘褜鍨堕弻锟犲椽娴g顫梺閫涚┒閸斿矂锝炲鍫濆耿婵°倐鍋撶紒鐘茬秺濮婅櫣绱掑Ο缁樺創缂傚倸绉崇粈渚€锝炶箛鏃傜瘈婵﹩鍓涢敍婊冣攽閻愭潙鐏﹂柣鐔濆懐鐭欓柛銉墯閳锋垿鏌熼懖鈺佷粶濠德ゅ亹缁辨挸顓奸崪鍐ㄤ紣濡炪値鍋勭换鎴犵矉閹烘柡鍋撻敐搴樺亾椤撳﹤娲ㄩ崣鎾绘煕閵夛絽濡界紒鈧崼鐔翠簻闁挎棁顕у▍宥嗘叏婵犲嫮甯涚紒妤冨枛瀹曟儼顦辩紒銊у帶閳规垿顢欑憴鎺撶矒瀹曟繈骞嬮敃鈧粻鏍煥閻斿搫校闁哄懏鎮傞弻锝呂熼崹顔炬濡炪倖鎸搁妶绋款潖濞差亝鐒婚柣鎰蔼鐎氭澘顭胯椤曨參鍩€椤掍緡鍟忛柛鐘崇墵閹ê顫濇潏銊ュ簥闂佸憡渚楅崹鎶藉础濮樿埖鍊甸柛锔诲幘濮e攨cipants - * Participant闂傚倸鍊搁崐椋庣矆娴i潻鑰块梺顒€绉埀顒婄畵瀹曞ジ濡烽妷褝绱柣鐔哥矌婢ф鏁幒妤佸珔闁绘柨鍚嬮悡鏇㈡煏婢舵ê鏋涘褜鍨堕弻锟犲椽娴g顫梺閫涚┒閸斿矂锝炲鍫濆耿婵$偛澧介弫鏍⒒娴h銇熼柛妯恒偢閹矂宕掗悙鑼舵憰闂侀潧鐗嗗Λ娑㈡⒔閸曨偒鐔嗛悹杞拌濡叉椽鏌i埡浣规崳缂佽鲸鎸婚幏鍛驳鐎n亝鏆梻浣哄帶閸熸寧鏅舵惔銊ョ闁圭儤鏌¢崑鎾绘晲鎼粹€愁潽闁诲孩纰嶅畝鎼佸蓟閵堝洤鏋堥柛妤冨仜椤偊姊洪幐搴b姇闁烩晩鍨伴~蹇撁洪鍕唶闁硅壈鎻徊鍝勎i崼銉︹拺闁告繂瀚晶閬嶆煕閹捐泛鏋庨柣锝夋敱閵堬箑鈻庨悙顑芥瀸濠电姵顔栭崰鏍晝閵娿儳鏆﹂柣銏犵仛椤洘绻涢崱妯诲鞍闁稿鍔欓弻锝夊閳藉棗鏅遍梺鍝ュ枎缁夌懓顫? - * @async - * @returns {Promise} - */ async hangUp() { - this.clearStatsMessage(); // 濠电姷鏁告慨鐑藉极閹间礁纾婚柣鎰惈缁犱即鏌熼梻瀵割槮缂佺姷濮垫穱濠囶敍濠靛嫧鍋撻埀顒勬煛鐎n亞效妤犵偞鐗楀蹇涘礈瑜庨崑褏绱掗悙顒€鍔ら柕鍫熸倐瀵鏁愭径濠勭潉闂侀€炲苯澧い顏勫暣瀹曠螖閳ь剛鎲撮敃鍌涚厓鐟滄粓宕滃杈ㄥ床婵炴垯鍨洪弲鏌ユ煕濞戝崬鏋ゆい锕備憾濮婃椽宕ㄦ繝鍐ㄩ瀺缂備浇顕ч崯浼村焵? - this.stopNetworkQualityDetection(); // 闂傚倸鍊搁崐鐑芥嚄閸洍鈧箓宕奸妷顔芥櫈闂佺硶鍓濋悷銉╁垂濠靛牃鍋撻獮鍨姎妞わ缚绮欏顐﹀幢濡偐顔曢梺鐟邦嚟閸嬬偤鎯冮幋鐘垫/濡わ絽鍟伴悾娲煛瀹€鈧崰鎾诲焵椤掑倹鏆╂い顓炵墕閺嗏晠姊绘担渚敯妞ゎ偄顦叅闁绘梻鍘х粻鏍喐閻楀牆绗掔紒鐘崇洴閺屽秵娼幍顕呮М濡炪値鍋勫ú顓烆潖濞差亝顥堥柍鍝勫暙閸╁矂姊洪崷顓涙嫛闁稿顦甸幃妯尖偓锝庡枟閳锋垿姊洪銈呬粶闁兼椿鍨遍弲鍫曨敋閳ь剙螞? // 闂傚倸鍊搁崐鐑芥嚄閸洍鈧箓宕奸妷顔芥櫈闂佺硶鍓濋悷銉╁垂濠靛牃鍋撻獮鍨姎妞わ缚绮欏顐﹀幢濡偐顔曢梺鐟扮摠閻熴儵鎮橀鍫熺厽闁圭儤姊荤敮娑㈡煙娓氬灝濡奸摶锝夋煣韫囨洘顏熺紒杈╁仜閳规垿鍩ラ崱妤冧哗闂佺粯顨嗛幑鍥ь嚕婵犳碍鏅搁柣妯垮皺椤︺劑姊洪棃娴ゆ盯宕橀鍕闂傚倸鍊风粈渚€骞栭鈶芥稑顭ㄩ崟顓ф锤濡炪倖甯掔€氼剛绮婚鐐村€甸柨婵嗛閺嬫盯鏌i幒鎾淬仢闁哄本鐩獮鍥Ω閿旂晫褰熸繝纰樷偓铏枙闁搞劏娉涢~? + this.clearStatsMessage(); + this.stopNetworkQualityDetection(); if (this.durationInterval) { clearInterval(this.durationInterval); this.durationInterval = null; } - // 闂傚倸鍊搁崐鎼佸磹閻戣姤鍊块柨鏇氶檷娴滃綊鏌涢幇鍏哥敖闁活厽鎹囬弻娑㈩敃閿濆棛顦ㄩ梺绋款儛娴滎亪寮诲☉銏犵労闁告劦浜栨慨鍥⒑缂佹ê绗х紒顕呭灦楠炲牓濡搁妷搴e枛閹煎綊鎯傞崫銉ь槸濠电姷顣藉Σ鍛村垂娴煎瓨鍋嬮柟鎹愵嚙閽冪喖鏌ㄩ悢鍝勑㈢紒鈧崘顔界厪濠电倯鍐ㄦ殶闁告ü绮欏缁樻媴閸涘﹥鍎撳┑鈽嗗亝缁诲啰绮嬪鍥ㄥ磯闁惧繗顫夊▓楣冩⒑闂堟冻绱¢柛灞剧☉婵壆绱撻崒姘偓鐑芥⒔瀹ュ鍨傞悷娆忓缁诲棝姊洪鈧粔鐢稿煕閹达附鐓曟繝闈涙椤忣偆鈧稒绻傞埞鎴︽倷閼碱剚鍕鹃梺绋匡攻椤ㄥ﹪鐛? this.durationSynced = false; - const isHost = this.role === 'host'; console.log(`Disconnect peer on ${this.connectionId}. Role: ${this.role}`); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹闂佺粯鍔曢顓犵不妤e啯鐓冪憸婊堝礈濮樿鲸宕叉繛鎴欏灩瀹告繃銇勯幘璺哄壉闁告柨顦埞鎴︽倷闂堟稑浠樺銈庡幘閸忔﹢鐛崘銊㈡瀻闁规儳纾鍥⒑閻熸壆鎽犵紒璇茬Т鍗辩紓浣姑肩换鍡涙煏閸繃鎼愰崯鎼佹⒑缁嬫鍎戝┑顔芥尦閸┿垽寮惔鎾搭潔濠电偛妫欓崺鍫澝归崟顖涒拺缂備焦鈼ら鍕庣喐绻濋崒銈囧姺闂佺粯顨呴悧鍕濠婂牊鐓忛煫鍥ь儏閻忣喗銇勯埡鍐簮RTC if (this.renderstreaming) { try { - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鍐蹭画闂佹寧娲栭崐鎼佸垂閸岀偞鐓曠憸搴ㄣ€冮崨瀛樺€块柛顭戝亖娴滄粓鏌熸潏鍓хɑ缁绢厼鐖奸弻娑㈠棘鐠恒剱褔鏌″畝瀣?濞寸媴绠撻幊鐐哄Ψ閿旇棄鈧攱淇婇悙顏勨偓鏍洪埡鍐濞撴埃鍋撶€规洘妞介崺鈧い鎺嶉檷娴滄粓鏌熼崫鍕ら柛鏂跨Т闇夋繝褍鐏濋埀顒佺箞瀵鏁愭径濠勭杸濡炪倖鎸鹃崰鎾剁矙閸ャ劎绡€闁靛骏绲剧涵鍓х磼婢跺矈妫庣憸棰佺窔濮婃椽骞栭悙鎻掑Х缂備礁寮跺钘夌暦閹达箑绠婚悹鍥皺閻撴垶绻濋姀锝嗙【閼煎懘鏌熺€电啸缁惧彞绮欓弻娑氫沪閸撗勫櫘闂佸憡鏌ㄧ粔褰掑蓟閿濆鍋嗛柛灞剧矌閺嗙娀姊洪幐搴㈢8闁搞劏妫勯悾鐑藉醇閺囥劍鏅㈤梺閫炲苯澧扮紒顔肩墛瀵板嫰骞囬鐘插笚闂備線娼чˇ顓㈠礉瀹ュ鍋熸い蹇撶墛閻? - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢敂钘変罕闂佺硶鍓濋悷褔鎯岄幘缁樺€垫繛鎴烆伆閹达箑鐭楅煫鍥ㄧ⊕閻撶喖鏌¢崘銊モ偓鍝ユ暜閸洘鈷掗柛灞诲€曢悘锕傛煛鐏炵偓绀冪紒缁樼洴瀹曞綊顢欓悷棰佸濠德板€曢崯浼存偝缂佹ü绻嗛柕鍫濇噺閸f椽鏌i幘瀵搞€掗柍褜鍓欑粻宥夊磿闁秵鍋嬫繝濠傚枤閻掍粙鏌熼柇锕€鏋撻柛瀣尵閹叉挳宕熼鈧惌顕€姊洪悡搴℃毐闁绘牕銈搁悰顔藉緞閹邦剛顔掑銈嗘閸嬫劙顢欐繝鍥ㄢ拺闁告繂瀚婵嬫煕鎼淬垹濮囬摶鐐碘偓鍏夊亾闁告洦鍏橀幏娲⒑閸涘﹦鈽夐柨鏇樺€栭幈銊╁炊椤掍胶鍘遍梺缁樻閺€閬嶅磻閵夆晜鐓欐い鏃€鍎虫禍楣冩煏閸剛绉€规洘锕㈤崺锟犲磼濞戞艾寤? - // - host闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢敃鈧壕濠氭煕閳╁啰鈽夐柛灞诲姂閺屻倕霉鐎n偅鐝曞銈庡亝濞茬喖寮婚悢鍛婄秶濡わ絽鍟宥夋⒑閸濆嫭濯奸柛鎾村哺楠炲牓濡搁妷搴e枛閹煎綊鏌呭☉銏犱粣闂傚倷鑳堕…鍫ヮ敄閸℃稑绠伴柟闂寸筏缂嶆牗绻濋棃娑欏缂傚秴娲弻宥嗘姜閹殿噮妲紓鍌欒閺呯姴顫忕紒妯诲闁告稑锕ㄧ涵鈧俊鐐€х徊鑲╁垝濞嗗繒鏆﹂柟杈剧畱缁犺崵绱撴担璇$劷闁告ɑ鎮傚娲礈閹绘帊绨肩紓浣告惈濞硷繝宕洪埀顒併亜閹哄棗浜鹃梺浼欑悼閺咁暁icipants闂傚倸鍊搁崐鐑芥倿閿旈敮鍋撶粭娑樻噽閻瑩鏌熸潏楣冩闁搞倖鍔栭妵鍕冀椤愵澀绮堕梺鎼炲妼閸婂骞夐幖浣瑰亱闁割偅绻勯悷鏌ユ⒑閹惰姤鏁辨俊顐㈠暣瀵寮撮姀鐘诲敹濠电娀娼уù鍌毼涢悙鐑樷拺缂備焦蓱閹牏绱撳鍕槮妞? - // - participant闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢敃鈧壕濠氭煕閳╁啰鈽夐柛灞诲姂閺屻倕霉鐎n偅鐝曞銈庡亝濞茬喖寮婚悢鍛婄秶濡わ絽鍟宥夋⒑閸濆嫭濯奸柛鎾村哺楠炲牓濡搁妷顔藉缓闂佺硶鍓濋妵鐐佃姳娴犲鈷戞繛鑼额嚙楠炴牠鏌i鐐测偓鎼侊綖韫囨拋娲敂瀹ュ棙娅囬梻浣瑰缁诲倸螞濞戞ǚ鏋旈柕鍫濐槹閳锋垹绱撴担鑲℃垿骞嗛崟顖涚厵缂佹稑顑嗙壕鐥祎闂傚倸鍊搁崐鐑芥倿閿旈敮鍋撶粭娑樻噽閻瑩鏌熺€电浠ч梻鍕閺岋繝宕橀妸銉㈠亾閻熸壆鏆﹂柛娆忣槹閸欏繑淇婇姘变虎闁绘挻鍔欓弻娑氣偓锛卞嫭鐝曠紓浣虹帛閻╊垰鐣烽崡鐐嶇喖宕崟鍨秼闂傚倷鑳剁划顖炲箹椤愩倗绀婂ù锝呭濞? await this.renderstreaming.deleteConnection(); await this.renderstreaming.stop(); - } catch (error) { + } + catch (error) { console.error('Error during hangUp:', error); } this.renderstreaming = null; } - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢埛姘そ婵¤埖寰勭€n亙妲愰梻渚€娼ц墝闁哄懏鐩幏鎴︽偄鐏忎焦鏂€闂佺粯蓱瑜板啴寮抽悙瑁佺懓鈹冮崹顔瑰亾閺嶎偅宕叉繝闈涙川缁♀偓闂佺鏈粙鎴濃枍濠靛鈷戦柛婵嗗椤箓鏌涙繝鍐疄鐎殿喖顭烽弫鎰緞濞戞氨鈼ゆ俊鐐€栧濠氬磻閹炬枼鏀介柍銉ㄥ皺閻瑩鏌$仦鍓ф创闁糕晪绻濆畷鎺戭煥閸曨偄鐏¢梻鍌欐祰椤曟牠宕伴幒妤€鐤鹃柣妯款嚙缁犵姵绻濇繝鍌涘櫣闁哄鐗婃穱濠囶敍濠靛洢鈧啫顭跨憴鍕缂佺粯绋掑蹇涘礈瑜庨崑褔姊洪崫鍕妞わ箒浜崚鎺楀醇閳垛晛浜鹃柨婵嗛婢ф壆鐥幆褋鍋㈤柡灞剧〒娴狅箓宕滆閻g敻姊洪崨濠呭缂傚秴锕? this.updateRemoteUserStatus('offline'); this.updateRemoteUserNetworkQuality('no_signal'); - // 濠电姷鏁告慨鐑藉极閹间礁纾婚柣鎰惈缁犱即鏌熼梻瀵割槮缂佺姷濞€閺岀喖鎮ч崼鐔哄嚒缂備胶濮甸悧鏇㈠煘閹达箑鐏抽悗鍦Х濡澁ticipants this.state.participants = {}; this.selfParticipantId = null; this.connectionId = null; @@ -629,36 +448,28 @@ class CallStateManager { this.state.session.status = 'ended'; this.notify({ type: 'CALL_ENDED', reason: isHost ? 'host_hangup' : 'participant_hangup' }); } - _handleTrackEvent(data) { const direction = data.transceiver.direction; - if (direction === 'sendrecv' || direction === 'recvonly') { this._handleIncomingTrack(data); return; } - if (direction === 'sendonly' && data.track.kind === 'audio') { this.startActivityDetection(this.state.localStream, { isLocal: true }); } } - _handleIncomingTrack(data) { const trackParticipantId = data.participantId || this.connectionId; const isHost = this.role === 'host'; const targetStream = this._getOrCreateRemoteStream(trackParticipantId, isHost); - this._replaceTrackOfSameKind(targetStream, data.track); console.log('Added new track:', data.track.kind, 'for participant:', trackParticipantId); - if (isHost && !this.state.participants[trackParticipantId]) { this._upsertParticipant(trackParticipantId); this._notifyParticipantsUpdate(); this.broadcastParticipantsList(); } - this._notifyRemoteStreamUpdate(targetStream, trackParticipantId, isHost, data.track.kind); - if (this.state.session.remoteUser.status !== 'online') { this.updateRemoteUserStatus('online'); this.updateRemoteUserNetworkQuality('good'); @@ -669,12 +480,10 @@ class CallStateManager { }); this._startDurationTimer(); } - if (data.track.kind === 'audio') { this.startActivityDetection(this.state.remoteStream, { isLocal: false }); } } - _getOrCreateRemoteStream(trackParticipantId, isHost) { if (isHost) { if (!this.state.remoteStreams[trackParticipantId]) { @@ -682,24 +491,19 @@ class CallStateManager { } return this.state.remoteStreams[trackParticipantId]; } - if (this.state.remoteStream == null) { this.state.remoteStream = new MediaStream(); } - return this.state.remoteStream; } - _replaceTrackOfSameKind(targetStream, track) { const existingTracks = targetStream.getTracks().filter(existingTrack => existingTrack.kind === track.kind); existingTracks.forEach(existingTrack => { targetStream.removeTrack(existingTrack); console.log('Removed old track:', existingTrack.kind); }); - targetStream.addTrack(track); } - _notifyRemoteStreamUpdate(targetStream, trackParticipantId, isHost, trackKind) { const notifyStreamUpdate = () => { this.notify({ @@ -710,7 +514,6 @@ class CallStateManager { }); console.log('Notified UI about remote stream update'); }; - if (trackKind === 'audio' && targetStream.getVideoTracks().length === 0) { console.log('Audio track arrived first, delaying stream notification for video track...'); setTimeout(() => { @@ -720,13 +523,10 @@ class CallStateManager { }, 200); return; } - notifyStreamUpdate(); } - _handleRenderStreamingMessage(data) { - console.log('闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁革富鍘藉畷鍙夌箾閹存瑥鐏╃紒鐙呯稻缁绘繈妫冨☉姘暫濡炪們鍊愰崑鎾寸節?', data); - + console.log('收到信令消息:', data); switch (data.type) { case 'chat-message': this._handleChatMessage(data); @@ -744,15 +544,12 @@ class CallStateManager { break; } } - _handleChatMessage(data) { const chatPayload = data.data || data.message; if (!chatPayload) { return; } - chatMessage.handleChatMessage(chatPayload); - if (data.participantId && this.role === 'host' && this.state.participants[data.participantId]) { this._upsertParticipant(data.participantId, { id: chatPayload.senderId, @@ -763,7 +560,6 @@ class CallStateManager { this.broadcastParticipantsList(); return; } - if (!this.role || this.role !== 'host') { if (data.participantId && this.state.participants[data.participantId]) { this._upsertParticipant(data.participantId, { @@ -771,7 +567,8 @@ class CallStateManager { ...(chatPayload.senderAvatar ? { avatar: chatPayload.senderAvatar } : {}) }); this._notifyParticipantsUpdate(); - } else if (chatPayload.senderId !== this.state.session.localUser.id) { + } + else if (chatPayload.senderId !== this.state.session.localUser.id) { this._updateRemoteUserProfile({ id: chatPayload.senderId, name: chatPayload.senderName, @@ -780,23 +577,19 @@ class CallStateManager { } } } - _handleMediaStateChangedMessage(data) { - console.log('闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁归棿绀侀悡姗€鏌熸潏鎯х槣闁轰礁绉电换娑㈠箣閻戝棛鍔烽梺鑽ゅ枑閸旀妲愰幘璇茬<婵ɑ鐦烽姀掳浜滈柟瀛樼箥濡偓濡ょ姷鍋涢崯鎾春閿熺姴宸濇い鏂垮悑閻ゅ倻绱撴担绋库挃濠⒀勵殜閺佸绻涚€涙鐭嬮柛搴㈠▕濠€渚€姊洪幐搴g畵婵炴潙鍊块幃鐐哄礈瑜忕壕鐣屸偓骞垮劚鐎氼喚绮i弮鍫熺厸?', data.data, 'from participant:', data.participantId); - + console.log('收到媒体状态更新:', data.data, 'from participant:', data.participantId); if (this.role === 'host') { if (data.participantId && this.state.participants[data.participantId]) { this._upsertParticipant(data.participantId, { mediaState: data.data }); } - this.updateRemoteMedia(data.data, data.participantId); this._notifyParticipantsUpdate(); this.broadcastParticipantsList(); return; } - if (data.participantId && data.participantId !== this.selfParticipantId && this.state.participants[data.participantId]) { this._upsertParticipant(data.participantId, { mediaState: data.data @@ -804,22 +597,18 @@ class CallStateManager { this._notifyParticipantsUpdate(); return; } - if (data.participantId === this.selfParticipantId) { return; } - console.log('Received media-state-changed from Host, updating remoteUser:', data.data); this.updateRemoteMedia(data.data, data.participantId); this._notifyParticipantsUpdate(); } - _handleUserInfoMessage(data) { - console.log('闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁秆勵殔閽冪喖鏌ㄥ┑鍡╂缂傚秵鐗楅妵鍕箳閸℃ぞ澹曞┑鐘殿暯閳ь剝灏欓惌娆撴煛鐏炲墽娲撮柛鈺嬬節瀹曟帒鈹冮幆褜娼撶紓鍌氬€风粈渚€鎯岄崒娑氼洸闁割偅娲栭弰銉╂煕閺囥劌鐏犵紒鈧崘顏呭枑闊洦娲滈惌鍡涙煃?', data.data, 'from participant:', data.participantId); + console.log('收到用户信息:', data.data, 'from participant:', data.participantId); if (!data.data) { return; } - if (data.participantId && this.role === 'host') { this._upsertParticipant(data.participantId, { id: data.data.id || '', @@ -830,60 +619,50 @@ class CallStateManager { this.broadcastParticipantsList(); return; } - this._updateRemoteUserProfile({ id: data.data.id || this.state.session.remoteUser.id, name: data.data.name || this.state.session.remoteUser.name, avatar: data.data.avatar || this.state.session.remoteUser.avatar }); } - _handleParticipantsSyncMessage(data) { if (this.role === 'host' || !data.data) { return; } - - console.log('闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁秆勵殔閽冪喖鏌i弮鍥モ偓鈧柛瀣尭閳藉鈻嶉褌绨奸柟渚垮姂瀹曞爼顢楁担鍝勫箥闂備礁鎲¢悷銉┧囬鐐茬厺闁哄洨濮崑鎾舵喆閸曨剛顦ㄩ梺鑹邦潐瀹曟﹢鎮橀崘顔解拺闁告稑锕ョ壕鐢告煛閸屾瑧绐旂€规洘鍨块獮妯兼嫚閼碱剦妲版俊鐐€栧Λ浣圭珶閸綆鏉洪梻鍌欐祰椤曆呮崲閹烘纾婚柣鏂垮悑閹偤骞栧ǎ顒€濡肩紒鈧?', data.data); + console.log('收到成员同步列表:', data.data); this.state.participants = omitParticipant(data.data, this.selfParticipantId); this._notifyParticipantsUpdate(); this._syncCallDuration(data.callDuration); } - _updateRemoteUserProfile(profile) { this._setRemoteUserState(profile); this._notifyRemoteUserChange({ mediaState: this.state.session.remoteUser.mediaState }); } - _syncCallDuration(callDuration) { if (this.durationSynced || typeof callDuration !== 'number') { return; } - this.state.session.duration = callDuration; this.durationSynced = true; this._startDurationTimer(); this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration }); console.log(`Call duration synced: ${callDuration} seconds`); } - _startDurationTimer() { if (this.durationInterval) { return; } - this.durationInterval = setInterval(() => { this.state.session.duration++; this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration }); }, 1000); } - _setRemoteUserState(patch) { this.state.session.remoteUser = { ...this.state.session.remoteUser, ...patch }; } - _setRemoteUserMediaState(mediaState) { this._setRemoteUserState({ mediaState: { @@ -892,7 +671,6 @@ class CallStateManager { } }); } - _notifyRemoteUserChange(changes = {}) { this.notify({ type: 'REMOTE_MEDIA_CHANGE', @@ -902,11 +680,6 @@ class CallStateManager { }); this._notifyUserListUpdate(); } - - /** - * 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鍐蹭画闂佹寧娲栭崐鎼佸垂閸岀偞鐓曠憸搴ㄣ€冮崨瀛樺€块柛顭戝亖娴滄粓鏌熸潏鍓хɑ缁绢厼鐖奸弻娑㈠棘鐠恒剱銈囩磼鏉堛劌绗氱€垫澘瀚禒锕傛寠婢跺﹦顓哄┑? * @param {string} type - 濠电姷鏁告慨鐑藉极閹间礁纾婚柣鎰惈閸ㄥ倿鏌i姀鐘冲暈闁稿顑呴埞鎴︽偐閹绘帗娈銈嗘礋娴滃爼寮诲☉妯锋闁告鍋熸禒顖炴煟韫囨挾绠抽柡浣割煼楠炲啳銇愰幒鎴犲€炲銈呯箰鐎氼喖袙閵忋倖鈷? - * @param {Object} data - 濠电姷鏁告慨鐑藉极閹间礁纾婚柣鎰惈閸ㄥ倿鏌i姀鐘冲暈闁稿顑呴埞鎴︽偐閹绘帗娈銈嗘礋娴滃爼寮诲☉妯锋婵炲棙鍔楃粙鍥⒑閸濆嫷鍎庣紒鑸靛哺瀵鏁愰崨鍌涙閸┾偓妞ゆ帒瀚崑瀣煕閳╁啰鎳呴柣? - */ sendMessage(type, data) { if (this.renderstreaming) { this.renderstreaming.sendMessage({ @@ -915,32 +688,19 @@ class CallStateManager { }); } } - - /** - * Participant state helpers - */ _notifyParticipantsUpdate() { this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); } - _upsertParticipant(participantId, patch = {}) { return upsertParticipant(this.state.participants, participantId, patch); } - _removeParticipant(participantId) { return removeParticipant(this.state.participants, participantId); } - - /** - * Host缂傚倸鍊搁崐鎼佸磹閻戣姤鍊块柨鏇楀亾閾荤偤鐓崶銊р槈闁搞劌鍊块弻鐔封枔閸喗鐏撴繛鎴炴尭缁夊爼鍩€椤掆偓缁犲秹宕曢柆宓ュ洭顢涘鍛瑝缂傚倷鐒﹁彠濞存粍绮撻弻鐔煎箲閹邦厾銆愭繛瀵稿閸ャ劎鍘搁梺绯曞墲閿氶柛婵囨そ閺岋紕浠︾粙鍨拤闂佺懓鍢查幊妯虹暦椤愶箑唯闁挎棁妫勯弸娑㈡⒒閸屾瑧鍔嶉柟顔肩埣瀹曟繂顓奸崨顖涙畷缂備礁顑堝▔鏇㈠汲閿曞倹鐓忓┑鐐茬仢閸撳鏌i弮鍌氬付濞磋偐濞€閺屾盯寮撮妸銉ョ缂備緡鍠楅幐鍐差潖濞差亝鍤掗柕鍫濇噸濞岊亞绱撴担鍓插剰缂佺粯锚閻e嘲鈹戦崶銊ュ妳闂佹寧绻傞幊搴ㄦ倶婵犲啰绠鹃柡澶嬪灥閹垶绻涢崗鑲╂噮缂侇喖顭锋俊鐑藉煛閸屾粌甯楅柣鐔哥矋缁挸鐣峰鍫熷亜濠靛倸顦▓銊╂⒑閸撹尙鍘涢柛瀣缁鈻庨幘瀵稿幍闁荤娀缂氬▍锝呯暤閸嶇彻cipant - * 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鍐蹭罕闂佸搫娲㈤崹鍦不閻樿绠规繛锝庡墮婵¤偐绱掗悩鍐插摵闁哄本鐩弫鍌滅驳鐎n亜顫噑t闂傚倸鍊搁崐鐑芥嚄閸洖鍌ㄧ憸鏃堝Υ閸愨晜鍎熼柕蹇嬪焺濞叉悂姊洪幐搴g畵妞わ缚鍗宠棢濠电姴娲﹂悡鍐煕濠靛棗顏╅柍褜鍓欓…鐑藉春閻愬樊鍚嬪璺侯儑閸樻捇姊虹€圭姵銆冪紒鈧笟鈧鎶藉即閻樼數锛?+ 闂傚倸鍊搁崐椋庣矆娴i潻鑰块梺顒€绉查埀顒€鍊圭粋鎺斺偓锝庝簽閿涙盯姊洪悷鏉库挃缂侇噮鍨堕幃鈥斥槈閵忥紕鍘卞銈嗗姧缁茶法绮婚妷鈺傜厸濞达絽鎲¢幊姊icipant婵犵數濮烽弫鎼佸磿閹寸姴绶ら柦妯侯棦濞差亝鍋愰悹鍥皺椤︻厼鈹戦悩缁樻锭婵炲眰鍊濋、? - * Participant闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁秆勵殔閽冪喐绻涢幋鐑囦緵婵炲皷鏅滈妵鍕箳閹存繍浼岄梺閫炲苯澧い銊ョ墦楠炲骞栨担鍝ヮ唵闁诲繒鍋熼弲顐⑿掗姀锛勭閻庣數顭堥鎾剁磼閻樿櫕灏い鏇秮楠炴﹢顢欓挊澶夋睏婵$偑鍊栧Λ浣哥暦閸偁浠氶柟鎯板Г閳锋垿鎮归崶褍绾ч柟鐧哥秮閺岀喖鎼归锝呯3閻庢鍠氶弫濠氥€佸Δ鍛妞ゆ劧缍€缁躲垽姊绘担鐟邦嚋缂佽鍊胯棟濞寸厧鐡ㄩ崐璺ㄧ磽娴h偂鎴﹀矗韫囨柧绻嗘い鏍ㄧ箖椤忕娀鏌ㄥ☉妯夹eǎ鍥э躬椤㈡稑顫濋崡鐐╁徍闂備礁鎼惉濂稿窗閹捐鐒垫い鎺嶈兌閳洟鏌ㄥ顑芥斀妞ゆ柨鍚嬮崰姗€鏌″畝鈧崰鏍х暦閿濆棗绶炲┑鐐靛亾閻忓棝鏌f惔銈庢綈婵炲弶锕㈤幊婵嬪箲閹邦喕绨烽梻鍌欑閹测€趁哄澶婃濞撴埃鍋撶€? - */ broadcastParticipantsList() { - if (this.role !== 'host' || !this.renderstreaming) return; - + if (this.role !== 'host' || !this.renderstreaming) + return; const memberList = buildParticipantsSyncData(this.state.session.localUser, this.state.participants); - this.renderstreaming.sendMessage({ type: 'participants-sync', data: memberList, @@ -948,34 +708,27 @@ class CallStateManager { }); console.log('Broadcast participants list:', Object.keys(memberList)); } - - /** - * 闂傚倸鍊峰ù鍥х暦閸偅鍙忕€规洖娲ㄩ惌鍡椕归敐鍫綈婵炲懐濮撮湁闁绘ê妯婇崕鎰版煕鐎e吀閭柡灞剧洴閸╁嫰宕橀鍛珮缂備焦鍎宠ぐ鐐靛垝濞嗘挸钃熼柍銉﹀墯閸氬骞栫划鍏夊亾閸愭畫鐐烘⒒娴e憡璐¢柛搴涘€濋妴鍐幢濞戞锛熷┑鈽嗗灠閵堝€熴亹閹烘挻娅滈梺鍛婁緱閸犳牠寮抽崼銉︹拺缂備焦顭囩紓姘舵煕婵犲啰绠炵€殿喛顕ч埥澶婎潩閿濆懍澹曢梺鎸庣箓缁ㄨ偐鑺遍悾宀€纾奸柣妯哄船瀹撳棝鏌? - * 婵犵數濮烽弫鎼佸磻閻愬樊鐒芥繛鍡樻尭鐟欙箓鎮楅敐搴℃灍闁哄拋浜缁樻媴閸涘﹤鏆堥柦鍐憾閺岋綁鍩℃繝鍌滀桓閻庢鍠栭…宄扮暦閵娾晩鏁婇柤娴嬫櫇閻涱噣姊虹拠鎻掑毐缂傚秴妫欑粋宥夋嚋閻㈡娴?VP9/AV1闂傚倸鍊搁崐鐑芥倿閿旈敮鍋撶粭娑樻噽閻瑩鏌熸潏楣冩闁稿顑呴埞鎴︽偐闊厾绀€濠电偛妯婃禍婊呯不娴兼潙绠归柟纰卞幖閺嬬喖鏌i敐鍫殭闁宠鍨块崺銉╁幢濡ゅ啩鍖栫紓鍌欑贰閸n噣宕规导鏉戠厺鐎广儱鐗忛悿鈧┑鐐村灦閻熝囧储娴犲鈷戦梻鍫熶緱閻掗箖鏌涙惔銊ゆ喚妞ゃ垺妫冮崺锟犲川椤旈棿缂撴俊鐐€栭悧妤冨枈瀹ュ拋鍟呮繝闈涚墢绾惧ジ寮堕崼娑樺鐎规洖鐭傞弻娑㈠煛鐎n剛锛熼梺閫炲苯澧剧紓宥呮瀹曟粌鈽夐埗鍝勬喘椤㈡洟鏁傜憴锝嗗闂備礁婀遍…鍫澝归悜钘夌叀濠㈣埖鍔栭悡鐘崇箾閼奸鍤欓柣蹇曞█閹繝濡舵径瀣幐閻庡箍鍎辨鎼佺嵁濡ゅ懏鐓熼柟鐑樺焾閻撳ジ鏌$仦鐣屝ユい褌绶氶弻娑㈠箻鐠虹儤鐎婚梺?H264 High Profile - */ setCodecPreferences(participantId) { const capabilities = RTCRtpSender.getCapabilities('video'); - if (!capabilities || !capabilities.codecs || capabilities.codecs.length === 0) return; + if (!capabilities || !capabilities.codecs || capabilities.codecs.length === 0) + return; const { codecs } = capabilities; - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢敂缁樻櫈闂佸憡渚楅崹顏堝磻閹炬剚娼╅柣鎾抽椤偆绱撴担浠嬪摵闁圭懓娲悰顔碱潨閳ь剙顕i崼鏇炵闁绘鏁稿畵浣糕攽閻樻剚鍟忛柛鐘愁殘閹广垽宕橀鍡楁殫閻庣懓瀚槐纰挎繝鐢靛Х閺佹悂宕戦悙宸劷婵炲棙鎸哥憴锕傛倵閿濆骸鏋熼柡鍜佷邯濮婄粯鎷呴崨濠傛殘闁藉啳浜槐鎺斺偓锝庡亝瀹曞矂鏌熼妤€浜炬俊鐐€曠换鎰归崒姣硷絾绻濆顓涙嫼闂佸憡鎹佺亸娆撳储濞戞◤鐟邦煥閸涱厺妲愬Δ鐘靛仜閸燁垳鈧絻鍋愰崚鎺楀礂婢跺﹣澹曢梺鎸庢礀閸婂摜绮婚幎鑺ョ厸闁告劑鍔屾禍鐐翠繆閺屻儰鎲炬慨濠勭帛閹峰懐绮欓幐搴♀偓顖氣攽閻橆喖鐏柨鏇樺灲楠炲棗鐣濋崟顐わ紲濠殿喗顭堥崑鎰板礉瀹勯偊娓婚柕鍫濇噽缁犱即鏌eΔ鈧Λ婵嬪箚鐏炶娇鏃€鎷呴崗鍝ョ泿闂傚鍋勫ú锕傚箰閻愵剚娅犻柟鎵閻撴洟鎮楅敐搴′簼閻忓繑澹嗛埀顒€鐏氬妯尖偓姘煎幖椤曘儵宕熼銈嗘畷婵犵數濮抽懗鍓佺矆閸愨晝绡€闁汇垽娼ф禒锕傛煕閵娿儳鍩f鐐寸墵閸╋繝宕掑搴㈤敜濠碉紕鍋涢鍛偓娑掓櫊閿? let selectedCodecs = []; - const av1Codec = codecs.find(c => c.mimeType === 'video/AV1'); const vp9Codec = codecs.find(c => c.mimeType === 'video/VP9'); - const h264HighCodec = codecs.find(c => - c.mimeType === 'video/H264' && - c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=6400') - ); + const h264HighCodec = codecs.find(c => c.mimeType === 'video/H264' && + c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=6400')); const h264Codec = codecs.find(c => c.mimeType === 'video/H264'); - - if (av1Codec) selectedCodecs.push(av1Codec); - if (vp9Codec) selectedCodecs.push(vp9Codec); - if (h264HighCodec) selectedCodecs.push(h264HighCodec); - if (h264Codec && (!h264HighCodec || h264Codec !== h264HighCodec)) selectedCodecs.push(h264Codec); - - if (selectedCodecs.length === 0) return; - + if (av1Codec) + selectedCodecs.push(av1Codec); + if (vp9Codec) + selectedCodecs.push(vp9Codec); + if (h264HighCodec) + selectedCodecs.push(h264HighCodec); + if (h264Codec && (!h264HighCodec || h264Codec !== h264HighCodec)) + selectedCodecs.push(h264Codec); + if (selectedCodecs.length === 0) + return; if (this.renderstreaming) { const transceivers = this.renderstreaming.getTransceivers(participantId); if (transceivers && transceivers.length > 0) { @@ -989,7 +742,8 @@ class CallStateManager { videoTransceivers.forEach(t => { try { t.setCodecPreferences(selectedCodecs); - } catch(e) { + } + catch (e) { console.error('Error setting codec preferences:', e); } }); @@ -998,126 +752,80 @@ class CallStateManager { } } } - - /** - * 闂傚倸鍊峰ù鍥х暦閸偅鍙忕€规洖娲ㄩ惌鍡椕归敐鍫綈婵炲懐濮撮湁闁绘ê妯婇崕鎰版煕鐎e吀閭柡灞剧洴閸╁嫰宕橀鍛珬闂備礁鎽滄慨鏉懳涘┑瀣摕婵炴垯鍨归悞娲煕閹板苯鎳愮粻鎺楁⒒娴e憡鎯堟俊顐㈤叄瀹曟洟鎮界粙鑳憰濠电偞鍨惰彜闁哄閰i弻鐔兼焽閿曗偓婢ф澘霉閻撳海鐒告慨濠冩そ閺屽懘鎮欓懠璺侯伃闂佸憡蓱閹倿寮婚妶澶婄闁圭粯甯╅弳銏犫攽閳ュ啿绾ч柛鏃€鐟╅妴渚€寮崼婵嬪敹闂佺粯鏌ㄩ崲鍙夌珶閹炬枼鏀介柣鎰煐瑜把呯磼閹绘帗鍋ラ柍銉畵瀹曠厧顭块鍛槈妞ゎ偅绮撻崺鈧い鎺戝閽? - * 闂傚倸鍊搁崐椋庣矆娴i潻鑰块弶鍫氭櫅閸ㄦ繃銇勯弽顐粶缂佲偓婢舵劖鐓涢柛銉㈡櫅娴犙囨煛?maxBitrate 婵犵數濮烽弫鎼佸磻濞戙埄鏁嬫い鎾跺枑閸欏繐霉閸忓吋缍戠痪鎯ф健閺岋紕浠︾拠鎻掑闂佸搫顑勭粈渚€鍩為幋锕€纾兼繝濠傛捣閸斿摜绱撴担鐟板妞ゃ劌锕璇测槈閵忕姴宓嗛梺闈涱焾閸庢煡鎮橀幘鍓佺=濞达絽鎼瀷闂佺顑嗛崝妤€危閹版澘绠虫俊銈傚亾缂侇偄绉归弻鐔衡偓娑欘焽缁犳ê霉閻樿櫕缍戞い顏勫暣婵¤埖鎯旈垾鑼泿婵$偑鍊栭崹闈浳涘┑瀣瀬鐎广儱顦粻娑㈡煛婢跺孩纭舵い鎾存そ濮婅櫣绱掑鍡欏姼濠电偛鎳忓ú鏍暰闁瑰吋鐣崝宥夋偂? - * @param {string} [participantId] - 闂傚倸鍊搁崐鐑芥嚄閸洖纾块柣銏㈩焾閻ら箖鏌嶉崫鍕櫣缂佹劖顨嗘穱濠囧Χ閸涱喖娅ら梺绋款儏椤︿即濡甸崟顖毼ч柛銉ュ閺嗘涪icipant闂傚倸鍊搁崐鐑芥倿閿旈敮鍋撶粭娑樻噽閻瑩鏌熺€涙绠ラ柣鎺曞Г缁绘繈寮撮悩铏彎t缂傚倸鍊搁崐鎼佸磹閻戣姤鍊块柨鏇楀亾閾荤偤鐓崶銊р槈闁搞劌鍊归妵鍕冀閵娧佲偓鎺旂磼閻樿崵鐣洪柡灞剧洴婵$兘顢欓悡搴浇闂備胶顭堥鍐礉瀹ュ桅闁告洦鍨奸弫鍥煟閺冨牜妫戞い鎴濆€荤槐鎾存媴閹绘帊澹? - */ setVideoEncodingParameters(participantId) { - if (!this.renderstreaming) return; - + if (!this.renderstreaming) + return; const transceivers = this.renderstreaming.getTransceivers(participantId); - if (!transceivers || transceivers.length === 0) return; - - const videoTransceivers = transceivers.filter(t => - t.sender && t.sender.track && t.sender.track.kind === 'video' - ); - + if (!transceivers || transceivers.length === 0) + return; + const videoTransceivers = transceivers.filter(t => t.sender && t.sender.track && t.sender.track.kind === 'video'); for (const transceiver of videoTransceivers) { try { const sender = transceiver.sender; const params = sender.getParameters(); - if (!params.encodings || params.encodings.length === 0) { params.encodings = [{}]; } - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢妶鍥╃厠闂佺粯鍨堕弸鑽ょ礊閺嵮岀唵閻犺櫣灏ㄩ崝鐔兼煛閸℃劕鈧洟婀侀梺鎸庣箓閻楀嫰鍩€椤戣棄浜鹃梻浣侯焾椤戝棝骞戦崶褏鏆﹂柣鎴犵摂閺佸洭鏌i幇顔碱暭妞ゎ偅娲樼换婵堝枈婢跺瞼锛熼梺鎯х箰闁帮絽鐣疯ぐ鎺戦唶闁哄洨鍋犻幗鏇㈡倵閸忓浜鹃梺鍛婃处閸嬪棝宕愰悙宸富闁靛牆妫楃粭鍌涚箾閺夋垶鍠樼€规洘鍨剁粭鐔煎焵椤掆偓椤繐煤椤忓嫬绐涙繝鐢靛Т閸婂鎮烽妸銉㈡斀闁宠棄妫楁禍婵堢磼椤旇偐效濠碉紕鏁诲顕€宕煎┑鍫晣濠电偠鎻徊鍧楀箠閹惧顩烽柕蹇嬪€栭埛鎴︽煙閼测晛浠滈柛鏂哄亾闂備礁鎲¢崝鏇㈠疮椤栫偑鈧啯绻濋崶褑鎽曢梺缁樻⒒閳峰牓寮崟顖涚厵缂佸瀵ч幑锝夋煏閸喐绶叉い顏勫暣婵″爼宕卞Ο閿嬪婵犵數鍋涘鍓佸垝鎼粹垾锝夊箛閺夎法鐫勯梺鍓插亞閸犲孩绂掗搹鍦=濞达絽鎼崝瀣⒑閸欏鐛媔trate const videoTrack = sender.track; const settings = videoTrack ? videoTrack.getSettings() : {}; const height = settings.height || 1080; - const maxBitrate = getAdaptiveVideoBitrate(height); - params.encodings[0].maxBitrate = maxBitrate; params.encodings[0].scaleResolutionDownBy = 1.0; params.encodings[0].xGoogleMinBitrate = Math.floor(maxBitrate * 0.5); - - // 婵犵數濮烽弫鎼佸磻閻愬樊鐒芥繛鍡樻尭鐟欙箓鎮楅敐搴℃灍闁哄拋浜缁樻媴閸涘﹤鏆堥柦鍐含缁辨帞鈧綆鍋勭粭褏绱掗钘夊摵鐎规洩绻濋幃娆撳煛閸屾稒婢戦梻鍌欑劍閹爼宕曞鍫濆窛妞ゆ枮浣稿姦婵﹦绮幏鍛村川婵犲倹娈橀梺姹囧焺閸ㄨ京鏁幒鏇犱罕婵犵數濮撮敃銈団偓姘ュ妽缁傛帡宕奸妷锔惧幍闂傚倸鍊搁顓㈠礉瀹ュ鐓曢悗锝庡亞缁犵偤鏌$仦鍓ф创濠碉紕鍏橀崺鈩冪節閸愮偓顥涚紓鍌氬€风拋鏌ュ磻閹炬剚鐔嗛柤鎼佹涧婵牓鏌嶉柨瀣诞闁哄本鐩鎾Ω閵夈倗鏁栭柣蹇撶箰缁绘帞妲愰幘璇茬<婵ɑ鐦烽姀鈥茬箚妞ゆ劧绲垮ú瀵糕偓娈垮櫘閸嬪﹪銆佸▎蹇婃瀻闁绘劦鍎烽鍕厽閹肩补鍓濈拹鈩冦亜椤撶偟澧﹀┑鈥冲缁瑥鈻庨幆褎顓块梻浣稿閸嬪懎煤閺嵮勬殰闂傚倷绀侀悿鍥綖婢舵劕纾块柧蹇e亞椤╂彃螖閿濆懎鏆為柣鎾存礋閹﹢鎮欓棃娑楀闂佹眹鍔嶉崹鍧楀箖濡ゅ懏鍋¢梺顓ㄩ檮閳诲牓姊虹拠鈥虫灆缂侇喗鐟╅妴浣糕槈閵忕姈褔鏌涢妷銏℃珕婵炲牆鎲℃穱? - // 'maintain-resolution' 闂傚倸鍊搁崐椋庢濮橆剦鐒介柤濮愬€栫€氬鏌i弮鍌氬付缂佲偓婢跺备鍋撻崗澶婁壕闂佸憡娲﹂崜娆愮閸濆嫷娓婚柕鍫濇婵倿鏌涙繝鍐ㄥ鐎规洘鍨块獮姗€鎳滈棃娑欑€梻浣告啞濞诧箓宕戦崱妯碱洸闁规鍠氱壕钘壝归敐鍫殐婵炲牊姊归妵鍕晝閸屾瑦鍠氬Δ鐘靛仦閻楁濡堕敐澶婄闁冲搫鍊搁崝鎺撲繆閻愵亜鈧牠宕濊缁辩偤宕卞顫秮椤㈡稑鈽夊槌栧晭闂備焦瀵х换鍌炲箠韫囨柨绶為柛鏇ㄥ幘绾惧ジ鎮归崶銊ョ祷闁哄棛鍠栭弻宥堫檨闁告挻鐟╅幃妯侯潩鐠洪缚鎽曢梺闈浥堥弲娑滅箽闂備礁婀遍崕銈夈€冮崨顓у晠婵犻潧娲㈡禍婊堟煛閸愩劌鈧懓鈻嶉弴鐘冲枑闁绘鐗婄亸锕傛煛? if (params.degradationPreference !== undefined) { params.degradationPreference = 'maintain-resolution'; } - sender.setParameters(params); console.log(`Set video encoding: maxBitrate=${maxBitrate / 1000000}Mbps, scaleResolutionDownBy=1.0, xGoogleMinBitrate=${Math.floor(maxBitrate * 0.5)}${participantId ? ` for ${participantId}` : ''}`); - } catch (error) { + } + catch (error) { console.error('Error setting video encoding parameters:', error); } } } - - - /** - * 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼舵憰闂佹寧绻傞ˇ顖滅矆婢跺备鍋撻獮鍨姎妞わ富鍨堕幃鐐哄垂椤愮姳绨婚梺纭呮彧缁插墽娑甸懜鐐逛簻闁挎棁鍋愰悾鐢告煛鐏炲墽娲村┑鈩冩倐婵$柉顦存い鏃€甯掕灃闁绘﹢娼ф禒锕傛煕閵婏箑顕滈柟渚垮姂閸┾偓妞ゆ帒瀚悡銉︾節闂堟稒顥滄い鎺嬪灮缁辨挸顓奸崪鍐╂暰婵烇絽娲ら敃顏堛€侀弴銏犖ч柛婊€鐒﹀В澶愭⒒娴h鍋犻柛鏂块叄瀵偆鎷犻懠顒佹闁荤喐鐟ョ€氼參宕曢悢鍏肩厪闊洢鍎崇壕璺ㄧ磼婢跺﹦鍩f慨濠冩そ濡啫霉閵堝棛娲寸€规洘锕㈤、姗€鎮? - * 婵犵數濮烽弫鎼佸磻閻樿绠垫い蹇撴缁€濠囨煃瑜滈崜姘辨崲濞戞瑥绶為悗锝庡亞椤︿即鎮?MediaStreamTrack.applyConstraints() 闂傚倸鍊搁崐椋庢濮橆剦鐒介柤濮愬€栫€氬鏌i弮鍌氬付缂佲偓婢舵劖鐓欓弶鍫濆⒔閻i亶鏌i幒鎴犱粵闁靛洤瀚伴獮鎺戭吋閸♀晜鈻婂┑鐐茬摠閸ゅ酣宕规禒瀣摕闁挎繂顦伴弲婵喢归敐鍡楀Τ闁规儳澧庣壕濂告倵閿濆簼鎲炬俊顖楀亾婵$偑鍊戦崹娲晝閵忋倕绠栨繛鍡樻尭閸ㄥ倹銇勯幇鍓佹偧闂佽姤甯″濠氬磼濞嗘埈妲梺鍦拡閸嬪﹤鐣烽鐑嗘晬闁绘劘灏欓敍鐔哥節闂堟稑鈧悂骞夐敓鐘茬?鐎广儱顦伴悡鐔兼煛閸愩劌鈧摜鏁崼鏇熺厸? - * 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鍐叉疄婵°倧绲介崯顐も偓姘槹閵囧嫰骞掗崱妞惧婵$偑鍊ゆ禍婊堝疮閺夋垹鏆﹂柟鐑橆殕閸婄兘鏌熺紒妯虹瑨闁轰降鍊濆娲嚒閵堝懏鐎鹃梺鑽ゅ枂閸庢娊鍩€椤掍礁鍤柛鎾寸〒閸掓帗绻濋崶銊︽珖闂佺鏈粙鎾诲储闁秵鈷戦柛锔诲幖閸斿鏌涢妸銉у煟閽樼喖鏌嶉妷锕€澧繛鎾愁煼閹鈽夊▍铏灴瀹曟劙骞囬悧鍫㈠幍濡炪倖妫佸Λ鍕煝閺囩姭鍋撶憴鍕闁绘牕銈搁妴渚€寮崼婵嗚€垮┑顔筋殔濡寮插▎鎾粹拻闁稿本鐟︾粊鐗堜繆濡炵厧濡跨紒顔肩墛缁楃喖鍩€椤掑嫨鈧礁顫濇0婵囨櫍闂佺粯蓱閸撴艾顭囬悢濂夋富闁靛牆鎳愮粻浼存煟濡も偓閿曨亜鐣烽弴鐐嶇喐绺介挊澶岀Ш闁轰焦鍔欏畷銊╊敊鐠侯煈鏀ㄥ┑鐘茬棄閵堝懐姣㈤梺绋跨箲閿曘垹顕f繝姘櫜闁告稑鍊瑰Λ鍐春閳ь剚銇勯幒鍡椾壕闂佹寧娲忛崹钘夌暦閿熺姵鍊婚柛鈩冾殕濞? - * @param {number} width - 闂傚倸鍊搁崐鐑芥嚄閸洖纾块柣銏㈩焾閻ら箖鏌嶉崫鍕櫣缂佹劖顨嗘穱濠囧Χ閸涱喖娅ら梺绋款儏椤︾敻寮婚妸銉㈡斀闁糕剝顭囬ˇ閬嶆⒑缁嬫鍎愰柟鍛婂劤閳藉鎮界粙鍨獩濡炪倖娲栭幊蹇浰夋径鎰拺? - * @param {number} height - 闂傚倸鍊搁崐鐑芥嚄閸洖纾块柣銏㈩焾閻ら箖鏌嶉崫鍕櫣缂佹劖顨嗘穱濠囧Χ閸涱喖娅ら梺绋款儏椤︾敻寮婚妸銉㈡斀闁糕剝锕槐鐐测攽閻愯尙澧涙い銊ユ缁岃鲸绻濋崶鑸垫櫖濠殿喗锕╅崢浠嬪磿閹炬剚娓? - */ async changeResolution(width, height) { if (!this.state.localStream) { - showNotification('Local video stream is not available', 'error'); + showNotification('本地视频流不可用', 'error'); return; } - const videoTracks = this.state.localStream.getVideoTracks(); if (videoTracks.length === 0) { - showNotification('Failed to switch resolution', 'error'); + showNotification('视频轨道不可用', 'error'); return; } - const track = videoTracks[0]; const label = getResolutionLabel(height); - try { - // 婵犵數濮烽弫鎼佸磻閻樿绠垫い蹇撴缁€濠囨煃瑜滈崜姘辨崲濞戞瑥绶為悗锝庡亞椤︿即鎮?applyConstraints 闂傚倸鍊搁崐椋庢濮橆剦鐒介柤濮愬€栫€氬鏌i弮鍌氬付缂佲偓婢舵劖鍊甸柨婵嗛婢ф壆绱掗悩铏叆妞ゎ厼娼¢幊婊堟濞戞﹩娼旈梻浣虹帛閹告悂宕愭繝姘劦妞ゆ巻鍋撶紒鐘茬Ч瀹曟洟宕¢悙宥嗙☉閳诲酣骞橀弶鎴滅暗闂備線鈧偛鑻晶瀛樻叏婵犲啯銇濈€规洘顨婇幃鈩冩償閳ユ壙鎴︽⒒娴h鍋犻柛銊ㄥ亹娴滄悂顢旈崼婢箓鏌熼悧鍫熺凡闁绘劕锕ラ妵鍕敄濠靛嫬鈧倕顭囬幇鐗堢厵鐎瑰嫮澧楅崵鍥煙椤旂晫鐭婇摶锝夋煕韫囨挸鎮戞い鏂跨箻濮婂宕掑▎鎰偘濡炪倖娲橀悧鐘茬暦鐟欏嫬顕遍柡澶嬪灩閻ゅ洭鏌熼崗鑲╂殬闁告柨鐬肩划锝呂旈崨顔尖偓鍫曟煟閹邦厼绲婚柍閿嬫閺屽秶绱掑Ο娲绘闂佽鍠楅〃濠囧极閹邦厽鍎熼柍銉ョ-椤旀帗绻濋悽闈涗粶闁绘妫濋幃銉︾附缁嬭儻鎽曢梺鏂ユ櫅閸燁垱鍒婇幘顔藉仭婵炲棗绻愰顐ょ磼鐎n亝鍠樻慨濠冩そ楠炴牠鎮欓幓鎺濈€抽梺璇插缁嬪牓寮插☉鈶┾偓锕傚炊椤忓棛鏉稿┑鐐村灦椤倿鍩¢崘鈺佹瀾閻庡箍鍎遍ˇ浼村吹? await track.applyConstraints({ width: { ideal: width, max: width }, height: { ideal: height, max: height }, frameRate: { ideal: 30, max: 30 } }); - - console.log(`闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹闂佸湱鍎ら崵锕€鈽夊鍡欏弳闂佸憡娲嶉弲娆戝垝閹剧粯鈷戠憸鐗堝笚閿涚喖鏌i幒鐐电暤鐎规洘鍨甸埞鎴犫偓锝庡亞閸橀亶姊洪棃娑辨Ч闁搞劎鏁诲畷顖烆敃閳垛晜鐏侀梺闈涱槴閺呮粓鍩涢幋锔界厱婵炴垶锕銉х磼濡や礁绗氱紒缁樼〒閹风姾顦撮柣锝変憾閺岋綁鏁愰崶褍骞嬮悗瑙勬穿缁叉儳顕ラ崟顓濇勃闁告稑锕︽禍?${width}x${height}`); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢妶鍥╃厠闂佺粯鍨堕弸鑽ょ礊閺嵮岀唵閻犺櫣灏ㄩ崝鐔兼煛閸℃劕鈧洟濡撮幒鎴僵闁挎繂鎳嶆竟鏇㈡煟鎼淬値娼愭繛鍙壝悾婵堢矙鐠恒劍娈鹃梺鍛婎殘閸庢劕顭囬埡鍛仯闁惧繒鎳撻崝瀣煙閾忣偆鎳囨慨濠勭帛閹峰懘鎼归悷鎵偧缂傚倷绶¢崰鏍嚐椤栨粌寮查梻浣稿暱閹碱偊骞婅箛娑樼?鐎广儱顦伴悡鐔兼煛閸愩劌鈧摜鏁崼鏇熺厸閻庯絺鏅濈粣鏃堟煙椤旂瓔娈滄俊顐㈠暙閳藉鈻庨幇顒佺€惧┑鐘殿暜缁辨洟宕戝Ο鐓庡灊婵炲棙鎸搁拑鐔兼煥濠靛棭妯堥柡浣革躬閺屻倖鎱ㄩ幇顑藉亾閺囩喍绻嗗ù鐘差儐閳锋垵霉閸忚偐鎳冮柣锔界矒閺屾盯鏁愯箛鏇犘滃Δ鐘靛仜閸熸娊藝閸︻厸鍋撶憴鍕闁稿繑锕㈤妴浣割潨閳ь剟骞冨▎鎴炲磯闁? - // 480p: ~1Mbps, 720p: ~2.5Mbps, 1080p: ~4Mbps, 2K: ~6Mbps + console.log(`分辨率已切换为 ${width}x${height}`); const maxBitrate = getTargetResolutionBitrate(height); this._applyMaxBitrate(maxBitrate); - - // 婵犵數濮烽弫鎼佸磿閹寸姴绶ら柦妯侯棦濞差亝鏅滈柣鎰靛墮鎼村﹪姊虹粙璺ㄧ伇闁稿鍋ゅ畷鎴﹀Χ婢跺鍘繝鐢靛仜閻忔繃淇婄粙妫电懓顭ㄩ崘顏喰ㄩ梺鍝勭灱閸犳牠鐛崱姘兼Щ闂佸搫妫滄ご鎼佸Φ閸曨垰围闁告侗鍠栧▓妤呮⒑閸濆嫭婀伴柣鈺婂灦閻涱噣骞掑Δ鈧粻锝夋煟閹存繃顥為柨娑樻噹閳规垿鏁嶉崟顐$捕闂佸鏉垮妤犵偛鍟村畷鎺楁倷閼碱剛鏆梻浣筋潐閸庣厧螞閸曨垱鍊峰┑鐘插暔娴滄粓鐓崶銊﹀暗鐎涙繈姊洪崫鍕闁告ê澧藉Σ鎰板箻鐠囪尙锛滃┑顔斤供閸撴岸鎮橀幘缁樷拺闁告縿鍎辨牎闂佺粯顨堟慨鎾偩閻戣棄浼犻柛鏇ㄥ幗濞堟洟姊洪崨濠冨闁稿瀚伴幃闈浳熸笟顖涘瘜闂侀潧鐗嗗Λ妤呭锤婵犲洦鐓曢悗锝庡亝鐏忔壆绱掔紒妯肩疄闁糕斁鍋撳銈嗗笒閸婄敻宕戦幘缁樺仺闁汇垻鍋i埀顒€锕弻娑氣偓锝庡亝鐏忣參鏌嶉挊澶樻Ц闁宠绉归、妯款槺闂侇収鍨抽埀? const userSettings = JSON.parse(localStorage.getItem('userSettings') || '{}'); userSettings.resolution = { width, height }; localStorage.setItem('userSettings', JSON.stringify(userSettings)); - - // 闂傚倸鍊搁崐鎼佸磹妞嬪孩顐介柨鐔哄Т绾惧鏌涘☉鍗炲季婵炲皷鏅犻弻鏇熺箾閻愵剚鐝曢梺?UI 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢埛姘そ婵¤埖寰勭€n亙妲愰梻渚€娼ц墝闁哄懏鐩幏? this.notify({ type: 'RESOLUTION_CHANGED', resolution: { width, height, label } }); - showNotification('Switched to ' + label, 'success'); - } catch (error) { - console.error('闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹闂佸湱鍎ら崵姘洪鍛珖闂侀€炲苯澧撮柟顕€绠栭幃婊堟寠婢跺孩鎲伴梻渚€娼чˇ顓㈠磿閹跺壙鍥敃閿旇В鎷虹紓渚囧灡濞叉牗鏅堕懠顑藉亾閸忓浜鹃梺褰掓?缁€浣瑰閻樺磭绠剧€瑰壊鍠曠花濂告煟閹捐泛鏋戠紒缁樼箖缁绘繈宕掑顓燁唹闂備胶鎳撻崥瀣礉濞嗘挸钃熼柡鍥ュ灩閻愬﹪鏌曟繛鍨姢濞寸姴銈稿?', error); - showNotification('Failed to switch resolution. The camera may not support it.', 'error'); + showNotification('已切换为 ' + label, 'success'); + } + catch (error) { + console.error('切换分辨率失败:', error); + showNotification('切换分辨率失败,摄像头可能不支持该分辨率', 'error'); } } - - /** - * 闂傚倸鍊峰ù鍥敋瑜忛懞閬嶆嚃閳轰胶绛忔繝鐢靛У閻旑剛绱為弽褜鐔嗛悹杞拌閸庢劖绻涢崨顖毿ラ柍褜鍓欑粻宥夊磿闁单鍥敍濠ф儳浜炬慨姗嗗墻濡插憡銇勯鈩冪《闁圭懓瀚板畷顐﹀礋椤撶啘鐐寸節閻㈤潧浠滈柛姘儑閺侇噣骞掗弴鐘辩綍?sender 闂傚倸鍊风粈浣革耿闁秴鍌ㄧ憸鏃堝箖濞差亜惟闁靛鍠楃紞搴㈢節閻㈤潧校闁煎綊绠栧?maxBitrate - * @param {number} maxBitrate - 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢敂钘変罕闂佸憡鍔﹂崰鏍婵犳碍鐓欓柣鎰靛墮婢ь垶鏌i弬鎸庮棦闁哄本鐩、鏇㈠閳藉棙顥i梺璇插閸戝綊宕伴弽褍绁梻浣虹帛閸ㄥ吋鎱ㄩ妶澶嬪亗闁绘棃鏅茬换鍡涙煏閸繂鈧憡绂嶉幆褉鏀介柣姗嗗亜娴滈箖姊洪崨濠庢畼闁稿孩鍔欏畷顖炲川椤撴稒鏂€闂佺鏈喊宥夊疮閻愮儤鐓涢柛婊€绀佹晶鎾煛鐏炲墽顬兼い锔界叀閺屸剝鎷呴幓鎺嶅s闂? - */ _applyMaxBitrate(maxBitrate) { - if (!this.renderstreaming) return; - + if (!this.renderstreaming) + return; const isHost = this.role === 'host'; const participantIds = isHost ? Object.keys(this.state.participants) : [null]; - for (const pid of participantIds) { const transceivers = this.renderstreaming.getTransceivers(pid); - if (!transceivers) continue; - - const videoTransceivers = transceivers.filter(t => - t.sender && t.sender.track && t.sender.track.kind === 'video' - ); - + if (!transceivers) + continue; + const videoTransceivers = transceivers.filter(t => t.sender && t.sender.track && t.sender.track.kind === 'video'); for (const transceiver of videoTransceivers) { try { const sender = transceiver.sender; @@ -1128,19 +836,17 @@ class CallStateManager { params.encodings[0].maxBitrate = maxBitrate; sender.setParameters(params); console.log(`Updated maxBitrate to ${maxBitrate} for ${pid || 'self'}`); - } catch (error) { + } + catch (error) { console.error('Error updating maxBitrate:', error); } } } } - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢埛姘そ婵¤埖寰勭€n亙妲愰梻渚€娼ц墝闁哄懏鐩幏鎴︽偄鐏忎焦鏂€闂佺粯蓱瑜板啴寮抽悙瑁佺懓鈹冮崹顔瑰亾閺嶎偅宕叉繝闈涙川缁♀偓闂佺鏈粙鎴︻敁濞戞瑧绠鹃悗鐢殿焾鐢爼鏌涙繝鍐╁€愰柛鈺冨仱楠炲鏁傜紒妯绘珫婵犵數鍋為崹鍫曗€﹂崶顒佸仭闁归偊鍠氱壕浠嬫煕鐏炵偓鐨戠€涙繂顪冮妶鍡楃仴妞わ妇鏁婚悰顕€宕橀鑲╁幐闂佸憡渚楅崰鏍р枔濠靛牏纾奸柛鎾楀喚鏆梺鎸庤壘闇?(闂?WebSocket 闂傚倸鍊峰ù鍥х暦閻㈢绐楅柟鎵閸嬶繝寮堕崼姘珖濞戞挸绉归弻鐔告綇閸撗呫偡婵? updateRemoteMedia(mediaState, participantId) { this._setRemoteUserMediaState(mediaState); this._notifyRemoteUserChange({ mediaState, participantId }); } - updateRemoteUserStatus(status) { this._setRemoteUserState({ status }); this._notifyRemoteUserChange(); @@ -1149,7 +855,6 @@ class CallStateManager { this._setRemoteUserState({ networkQuality }); this._notifyRemoteUserChange(); } - _setSpeakingState(isLocal, isSpeaking) { if (isLocal) { this.state.session.localUser.mediaState.isSpeaking = isSpeaking; @@ -1157,91 +862,63 @@ class CallStateManager { this.emitMediaStateChange(); return; } - this.updateRemoteMedia({ isSpeaking }); } - // 缂傚倸鍊搁崐鎼佸磹閹间礁纾归柣鎴eГ閸婂潡鏌ㄩ弴鐐测偓鍝ョ不娴煎瓨鍋i柛銉戝嫧鏋欓梺缁樺笩婵倝濡甸崟顖氱疀闁割偅娲橀宥夋⒑缂佹ê绗х紒顕呭灦楠炲牓濡搁妷搴e枛閹煎綊鎯傞崫銉ь槸濠电姷顣藉Σ鍛村垂娴煎瓨鍋嬮柟鎹愵嚙閽冪喖鏌曟繛鍨姉婵℃彃鐗婃穱濠囶敍濠婂啫浠橀梺鎰佷邯娴滆泛顫忛悜妯诲濞寸厧鐡ㄩ鏍⒑缁嬪尅鏀荤紒璇茬墦閹即顢欓柨顖氫壕闁挎繂楠搁弸鐔兼煙鐏炲倸鍔﹂柡灞剧〒娴狅箓鎮欓鍌涱吇婵犲痉銈庡殝缂傚秳绀侀~蹇撁洪鍕獩婵犵數濮撮崯浼村储娴犲鈷戦柛娑橈攻閻撱儵姊虹敮顔惧埌闁伙絿鍏橀獮瀣晜閼恒儲鐝冲┑鐘灱濞夋盯鏁冮敃鍋瑰洭鍩¢崘顏嗭紳闂佺鏈悷褔宕濆澶嬬叆婵鍩栭悡鐔兼煥閺冨浂鍤欓柣蹇ョ畵閺屸€崇暆鐎n剛鐦堥悗瑙勬礃鐢剝淇婇崼鏇炲窛妞ゆ挾濯崯鍫ユ⒒娴g瓔鍤欏Δ鐘虫倐閹ê顫濋崡鐐茬亰闂佽宕橀褔鎷戦悢鍏肩叆闁哄洨鍋涙禍鐐烘煛娴e壊鍎旈柡灞剧洴閸╁嫰宕橀鍛珮婵? async endCall() { console.log(`endCall called. Role: ${this.role}`); - // 闂傚倸鍊峰ù鍥х暦閸偅鍙忛柟缁㈠櫘閺佸嫰鏌涘☉娆愮稇闁汇値鍠栭湁闁稿繐鍚嬬紞鎴︽煛?hangUp() 濠电姷鏁告慨鐢割敊閺嶎厼绐楁俊銈呭暞瀹曟煡鏌熼柇锕€鏋涚紒韬插€濋弻娑滎槼妞ゃ劌鎳橀幃姗€鎼归锝呭伎濠碉紕鍋犻褎绂嶆ィ鍐╁€甸悷娆忓缁€鍐┿亜閵娿儻韬柣娑卞枟閹棃濡搁敃鈧▓鐔兼⒑闂堟侗妯堥柛銊ュ暱椤?WebRTC 闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺鍝勬缁捇寮婚悢铏圭<闁靛繒濮甸悘鎾绘倵鐟欏嫭灏紒鑸靛哺楠炲啳銇愰幒鎴滅炊闂佸憡娲﹂崜姘跺磿閹惧墎纾藉ù锝囶焾缁狙勪繆閻愯埖顥夋い顐㈢箰鐓ゆい蹇撶У閺呮繈姊洪幐搴b槈閻庢凹鍓氭穱濠冪鐎n偀鎷洪梺鍛婄箓鐎氼垳鈧氨澧楃换娑氫沪閸屾埃鍋撳┑瀣ㄢ偓浣割潩閼稿灚娅滄繝銏f硾閿曪箓宕濋敃鈧—鍐Χ閸℃鐟愰梺鐓庡暱閻栧ジ宕洪悙宸悑濠㈣泛顑囬崢鎾绘⒑闂堟侗妲堕柛搴や含閻ヮ亣顦查柍? - // hangUp 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼槷闂佸搫娲㈤崹鍦不閻樿櫕鍙忔俊鐐额嚙娴滈箖鎮楃憴鍕婵炲弶绮撻崺鈧い鎺嶈兌閳洟鏌ㄥ顓滀簻闁哄浄绻濋崫娲煙娓氬灝濡兼い顐g矒瀹曞崬螖閳ь剛绮荤紒妯肩瘈闁靛繈鍨洪崵鈧梺娲诲幖閸婂灝顕f繝姘亜闁绘垶锚閻濅即姊洪悙钘夊姤婵炲懏娲熼幃姗€寮撮悜鍡樺瘜闂侀潧鐗嗘鍛婄濠靛洢浜滈柕濞垮劵闊剚顨ラ悙璇у伐闁宠鍨归埀顒婄秵娴滅偤顢欏畝鍕拺闁革富鍘奸崝瀣煕閵娿儳浠㈤柍缁樻崌椤㈡宕熼鑺ュ闂備礁鎲$粙鎴︽晝閵堝洨绠旈柟鐑樻尫缁诲棙銇勯幇鈺佺労闁搞倗鍠愭穱? - // - host: 闂傚倸鍊搁崐鎼佸磹妞嬪孩顐介柨鐔哄Т绾惧鏌涘☉鍗炲季婵炲皷鏅犻弻鏇熺箾閻愵剚鐝曢梺绋款儏閸婂潡寮婚妸鈺傚亜闁告繂瀚呴姀銏㈢<闁绘﹩鍠栭崝锕傛煛鐏炵晫啸妞ぱ傜窔閺屾盯骞樼捄鐑樼€诲銈嗘穿缂嶄礁鐣疯ぐ鎺濇晝闁靛牆娲ㄩ弳顖滅磽閸屾瑧鍔嶉柡灞诲姂椤㈡瑩骞夐崸鏄猧pants闂傚倸鍊搁崐鐑芥倿閿旈敮鍋撶粭娑樻噽閻瑩鏌熸潏楣冩闁搞倖鍔栭妵鍕冀椤愵澀绮堕梺鎼炲妼閸婂骞夐幖浣瑰亱闁割偅绻勯悷鏌ユ⒑閹惰姤鏁辨俊顐㈠暣瀵寮撮姀鐘诲敹濠电娀娼уù鍌毼涢悙鐑樷拺缂備焦蓱閹牏绱撳鍕槮妞? - // - participant: 婵犵數濮烽弫鎼佸磻濞戙埄鏁嬫い鎾跺枑閸欏繘鏌熺紒銏犳灈缂佺姷濞€閺岀喖骞戦幇闈涙闂佸憡淇洪~澶屾崲濠靛洨绡€闁稿本纰嶉悘鎾绘⒑閸濄儱鏋庨柟铏锝夊醇閺囩偤鍞跺┑顔斤供閸樹粙鎮甸敃鍌涒拺閻犲洤寮堕崬澶愭倶韫囨梻鎳囬柛鈹惧亾濡炪倖甯婄欢锟犲疮韫囨稒鐓曢柣妯哄暱濞搭喚鈧娲橀崹鍧楃嵁濡偐纾兼俊顖滃帶瀵櫕绻濋悽闈涗沪闁搞劍鍎奸幗顐⑩攽閻橆偄浜炬繛瀵稿帶閻°劑鎮¢弴銏$厓閻犲洠鈧啿绠瑰┑鐐额嚋缁犳捇宕哄☉銏犵闁哄啫鍊婚敍婵嬫⒑閸撴彃浜栭柛銊ф嚀閺嗏晝绱撻崒娆掑厡濠殿喖纾崚鎺戔枎閹惧疇鎽? await this.hangUp(); } - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鍨鹃幇浣圭稁缂傚倷鐒﹁摫闁告瑥绻橀弻鐔虹磼閵忕姵鐏堥梺娲诲幗椤ㄥ﹪寮诲☉銏犵労闁告劦浜栨慨鍥⒑缂佹ê绗х紒顕呭灦楠炲牓濡搁妷搴e枛閹煎綊鎯傞崫銉ь槸濠? async joinCall(connectionId) { this.state.session.status = 'connecting'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); - showNotification('Joining call (' + connectionId + ')'); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹濠德板€曢崯顖氱暦閺屻儲鐓曠€光偓閳ь剟宕曢幋鐘电闁哄稁鍘介悡娆撴煟濡も偓閻楀﹦娆㈤懠顒傜<闁? + showNotification('正在加入通话 (' + connectionId + ')'); await this.init(); - - // 婵犵數濮烽弫鎼佸磿閹寸姴绶ら柦妯侯棦濞差亝鏅滈柣鎰靛墮鎼村﹪姊虹粙璺ㄧ伇闁稿鍋ゅ畷鎴﹀Χ婢跺鍘繝鐢靛Т缁绘ê顬婇娴庣懓鈹冮崹顔瑰亾濠靛钃熼柨婵嗩槸缁犳稒銇勯幘璺烘灁缂佸娲︾换婵嬪閿濆棛銆愭繛鎴炴尭閼? this.connectionId = connectionId; } - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹濠德板€曢崯浼存儗濞嗘挻鐓欓悗鐢殿焾鍟哥紒鎯у綖缁瑩寮婚悢鐓庣鐟滃繒鏁☉銏$厽闁圭儤姊荤敮娑㈡煙娓氬灝濡奸摶锝夋煣韫囨洘顏熺紒杈╁仜閳? async createCall() { this.state.session.status = 'connecting'; this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); - showNotification('Creating call...'); - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹濠德板€曢崯顖氱暦閺屻儲鐓曠€光偓閳ь剟宕曢幋鐘电闁哄稁鍘介悡娆撴煟濡も偓閻楀﹦娆㈤懠顒傜<闁? + showNotification('正在创建通话...'); await this.init(); } - - // 闂傚倸鍊搁崐鐑芥倿閿曗偓椤啴骞愭惔锝庢锤闂佺粯鍔曢幖顐ょ玻濡ゅ懎绠规繛锝庡墮婵′粙鏌涚€e吀閭柡灞剧洴瀵挳濡搁妷銉ь啋缂備線绠栫粻鏍不閺嶎厼钃熼柣鏃囥€€閸嬫捇鎮藉▓璺ㄥ姼闁哥喐鎮傚铏规兜閸滀焦缍堝┑鐐跺皺閸犳牠鐛崘顓滀汗闁圭儤鍨归悿鈧梻浣哥枃濡椼劎绮堟笟鈧敐鐐哄炊椤掍讲鎷洪梻鍌氱墐閺呮繈宕氭导瀛樼厵缁炬澘宕禍顖炴煟韫囨搩鍎旀慨濠冩そ閺屽懘鎮欓懠璺侯伃婵犫拃灞芥珝婵? async detectNetworkQuality() { if (!this.renderstreaming) { return; } - try { const stats = await this.renderstreaming.getStats(); if (!stats) { return; } - const summary = summarizeInboundStats(stats); const quality = getNetworkQualityFromSummary(summary); - if (this.state.session.remoteUser.networkQuality !== quality) { this.updateRemoteUserNetworkQuality(quality); this.notify({ type: 'NETWORK_CHANGE', quality }); } - - } catch (error) { + } + catch (error) { console.error('Error detecting network quality:', error); } } - // 闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸劍閺呮繈鏌曟径娑橆洭缂佺姵鍎抽埞鎴︽偐閸欏鍋嶉梺閫炲苯澧柛濠傤煼楠炴垿宕熼鍌炴闂佺锕﹂崰鎰邦敂閻樼粯鐓欏瀣閳诲牊顨ラ悙瀛樺磳妤犵偞鍨块、鏇㈡晲鎼淬垻鏆﹀┑鐑囩到濞层倝鏁冮鍫涒偓渚€寮撮姀鈥充簻闂佺偓鑹鹃崐鎼佀夊顑芥斀? startActivityDetection(stream, { isLocal = false } = {}) { if (!stream) { return; } - const audioTracks = stream.getAudioTracks(); if (audioTracks.length === 0) { return; } - try { const { threshold, debounceTime, fftSize } = VAD_CONFIG; - const { analyser, dataArray } = createAudioAnalyser(stream, fftSize); let isSpeaking = false; let lastActivityTime = 0; - const detectActivity = () => { if (!stream || !this.renderstreaming) { return; } - const level = getAudioLevel(analyser, dataArray); - const currentTime = Date.now(); if (level > threshold / 100) { lastActivityTime = currentTime; @@ -1249,50 +926,39 @@ class CallStateManager { isSpeaking = true; this._setSpeakingState(isLocal, true); } - } else if (isSpeaking && currentTime - lastActivityTime > debounceTime) { + } + else if (isSpeaking && currentTime - lastActivityTime > debounceTime) { isSpeaking = false; this._setSpeakingState(isLocal, false); } - if (this.state.session.status === 'ongoing') { requestAnimationFrame(detectActivity); } }; - detectActivity(); - console.log(`${isLocal ? 'Local' : 'Remote'} activity detection started`); - - } catch (error) { + } + catch (error) { console.error(`Error starting ${isLocal ? 'local' : 'remote'} activity detection:`, error); } } - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鑼槱閻熸粎澧楃敮鎺楀垂閸岀偞鐓熸俊銈傚亾闁绘锕畷锝堢疀濞戞瑧鍘撻梺鍛婄箓鐎氼剟寮抽悢铏规/濡わ絽鍟伴悾娲煛瀹€鈧崰鎾诲焵椤掑倹鏆╂い顓炵墕閺嗏晠姊绘担渚敯妞ゎ偄顦叅闁绘梻鍘х粻鏍喐閻楀牆绗掔紒鐘崇洴閺屽秵娼幍顕呮М濡炪値鍋勫ú顓烆潖濞差亝顥堥柍鍝勫暙閸╁矂姊洪崷顓涙嫛闁稿顦甸幃妯尖偓锝庡枟閳锋垿姊洪銈呬粶闁兼椿鍨遍弲鍫曨敋閳ь剙螞? startNetworkQualityDetection() { - // 濠?缂傚倸鍊搁崐鎼佸磹妞嬪海鐭嗗〒姘e亾閽樻繈鏌熷▓鍨灍闁哄棙绮嶆穱濠囧Χ閸涱喖鍔欐繝銏e煐閸旀洖顔忓┑鍥ヤ簻闁圭儤鏌ㄧ敮鑸电節閳ь剟鏌嗗鍛姦濡炪倖甯掗敃锕€鐣濆☉銏$厸闁告侗鍠氱粻鐐淬亜閵忊剝鈷愭繛鐓庣箻婵℃瓕顦存い鏃€娲熷铏瑰寲閺囩噥娼戦梺鍛娒晶鑺ョ珶閺囩姵宕夐柕濞у拑绱抽柣搴$畭閸庨亶骞忕€n€綁鎼归崷顓狅紲闁哄鐗勯崝宀€绮幒鎾变簻妞ゆ挻绮屾慨鍌溾偓瑙勬礀瀹曨剝鐏冮梺鍛婂姦娴滄粓寮伴妷鈺傗拻? this.networkQualityInterval = setInterval(() => { this.detectNetworkQuality(); }, 3000); } - - // 闂傚倸鍊搁崐鐑芥嚄閸洍鈧箓宕奸妷顔芥櫈闂佺硶鍓濋悷銉╁垂濠靛牃鍋撻獮鍨姎妞わ缚绮欏顐﹀幢濡偐顔曢梺鐟邦嚟閸嬬偤鎯冮幋鐘垫/濡わ絽鍟伴悾娲煛瀹€鈧崰鎾诲焵椤掑倹鏆╂い顓炵墕閺嗏晠姊绘担渚敯妞ゎ偄顦叅闁绘梻鍘х粻鏍喐閻楀牆绗掔紒鐘崇洴閺屽秵娼幍顕呮М濡炪値鍋勫ú顓烆潖濞差亝顥堥柍鍝勫暙閸╁矂姊洪崷顓涙嫛闁稿顦甸幃妯尖偓锝庡枟閳锋垿姊洪銈呬粶闁兼椿鍨遍弲鍫曨敋閳ь剙螞? stopNetworkQualityDetection() { if (this.networkQualityInterval) { clearInterval(this.networkQualityInterval); this.networkQualityInterval = null; } } - - - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鍐蹭画闂佹寧娲栭崐鎼佸垂閸岀偞鐓曠憸搴ㄣ€冮崨瀛樺€块柛顭戝亖娴滄粓鏌熸潏鍓хɑ缁绢叀鍩栭妵鍕晜閼测晝鏆ら梺鍝勭焿缁蹭粙鍩ユ径濠庢僵妞ゆ帊鑳堕埀顒勭畺濮婃椽鎮烽弶鎸幮╅梺纭呮珪閿曘垽鎮伴鍢夌喖宕楅悡搴o紡闂備胶鍎甸弲婊呮暜椤忓棛涓嶉柟鎹愵嚙閽冪喖鏌i弮鍌楁嫛闁轰礁瀚伴幃瑙勭瑹椤栨粌甯ュ┑鈥虫▕閸o絽顫忛搹鐟板闁哄洨鍠愰悵鏃堟⒑濞茶骞栭柛濠冩倐椤㈡岸鏁愭径瀣垫濠电偞鍨靛畷顒勫箖閹达附鈷戦柛娑橈梗缁堕亶鏌涘▎蹇涱€楁い顓炴喘楠炲酣鎳為妷銏″? emitMediaStateChange() { const payload = { userId: this.state.session.localUser.id, ...this.state.session.localUser.mediaState }; console.log('[WebSocket Emit] media-state-changed:', payload); - // 婵犵數濮烽弫鎼佸磻閻樿绠垫い蹇撴缁€濠囨煃瑜滈崜姘辨崲濞戞瑥绶為悗锝庡亞椤︿即鎮楀▓鍨珮闁稿锕ㄥΛ銏㈢磽娴g绾х紒鏃偽朤C闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁撻悩鍐蹭画闂佹寧娲栭崐鎼佸垂閸岀偞鐓曠憸搴ㄣ€冮崨瀛樺€块柛顭戝亖娴滄粓鏌熸潏鍓хɑ缁绢叀鍩栭妵鍕晜閼测晝鏆ら梺鍝勭焿缁蹭粙鍩ユ径濠庢僵妞ゆ帊鑳堕埀顒勭畺濮婃椽鎮烽弶鎸幮╅梺纭呮珪閿曘垽鎮伴鍢夌喖宕楅悡搴o紡闂備胶鍎甸弲婊呮暜椤忓棛涓嶉柟鎹愵嚙閽冪喖鏌i弮鍌楁嫛闁轰礁瀚伴幃瑙勭瑹椤栨粌甯ュ┑鈥虫▕閸o綁骞冨畡閭︾叆闁告洦鍓涢崙鈥愁渻閵堝繒鐣垫繛浣冲洦鍋? if (this.renderstreaming) { this.renderstreaming.sendMessage({ type: 'media-state-changed', @@ -1300,70 +966,48 @@ class CallStateManager { }); } } - - // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢妶鍌氫壕婵鍘ф晶顖炴煛閸涙澘鐓愮紒鍌涘笧閳ь剨缍嗛埀顒夊弿闂勫嫰骞堥妸銉庣喐寰勭粙鎸庡創缂備胶鍋撻崕鎶藉Χ閹间礁钃熼柨婵嗩槸缁秹鏌嶈閸撴瑩鈥旈崘顔奸敜婵°倐鍋撻悷娆欑畵閺屽秷顧侀柛鎾存皑閹广垹鈽夐姀鈩冩珳闂佸憡渚楅崰姘端囬柆宥嗏拺闁告稑锕ョ亸鎵磼鐠囨彃鏆熼柍? async showStatsMessage() { console.log('Showing stats message'); - - // 缂傚倸鍊搁崐鎼佸磹閻戣姤鍊块柨鏇炲€搁拑鐔兼煏婵炵偓娅撻柡浣稿閺屾稑鈽夐崡鐐茬闂佸搫妫庨崐婵嬪蓟濞戙垹鐒洪柛鎰典簴濡插牏绱撴担鍝勑為柛搴㈠▕楠炲骞栨担鍝ヮ吅闂佹寧妫侀妴鈧柛瀣尰缁楃喖鍩€椤掑嫮宓侀悗锝庡枟閸婇鐥悧鍩虫垿鎮橀崘顔解拻濞撴埃鍋撻柍褜鍓涢崑娑㈡嚐椤栨稒娅犳い鏇楀亾闁哄苯绉堕幏鐘诲蓟閵夈儱鍙婇梻浣芥〃缁€浣虹矓閻熸壆鏆﹂柣鏃傗拡閺佸洭鏌eΟ娲荤劶闁告侗鍨抽敍婵嬫⒑缁嬫寧婀伴柣鐔濆洤绀夌€广儱娲ㄧ壕鍏笺亜閺冨洤浜圭紒鐘靛閵囧嫰寮捄銊ь唶闂佸疇顫夐崹褰掑焵椤掑﹦绉甸柛瀣瀹曘垽骞掑Δ渚囨濡炪倖鍔戦崹鐑樺緞閸曨兛绻嗘い鎰╁灮缁犵増銇勯銏㈢闁诡喗鐟╅、妤呭磼濠婂骸鏅? await this.detectNetworkQuality(); - - // 闂傚倸鍊峰ù鍥敋瑜嶉~婵嬫晝閸岋妇绋忔繝銏f硾閼活垶寮搁崼鈶╁亾楠炲灝鍔氶柟閿嬪灴閹虫捇宕稿Δ浣哄弳闂佺粯鏌ㄩ幖顐㈢摥闂備礁鎲¢悷銉╂晝椤忓牆钃熸繛鎴欏灩缁犵粯淇婇悙闈涗壕妞わ箓顥撶槐鎺楊敊閻愵剚姣堥梺鍝勭焿缂嶄線骞冮埡鍛煑濠㈣泛琚崑鎺戔攽閻樻鏆柍褜鍓濆▍鏇烆啅濠靛鐓曢柟鐑樻尭缁椻晝绱掗悩宕囨创鐎殿喗鎸抽幃鈺冨枈婢跺苯绨ラ梻鍌氬€烽懗鍓佸垝椤栨凹娼栭柣鐔煎亰濞撳鏌涜椤ㄥ懐鎲撮敃鍌涚厓鐟滄粓宕滃杈ㄥ床婵炴垯鍨洪弲鏌ユ煕濞戝崬鏋ゆい锕備憾濮婃椽宕ㄦ繝鍐ㄩ瀺缂備浇顕ч崯浼村焵? this.statsInterval = setInterval(async () => { if (!this.renderstreaming) { return; } - try { const stats = await this.renderstreaming.getStats(); if (!stats) { return; } - const statsSummary = summarizeInboundStats(stats); const statsLog = buildStatsLogPayload(this.state.session.remoteUser.networkQuality, statsSummary); - - // 闂傚倸鍊风粈渚€骞栭位鍥敍閻愭潙浜辨繝鐢靛Т濞层倗绮绘导瀛樼厵闂傚倸顕ˇ锕傛煕濮樻剚娼愰柕鍥у楠炴鎹勯惄鎺炵秮閹顫濋鐐叉懙闂佸搫琚崝鎴濐嚕椤掑嫬鍨傛い鏃囶潐鐎垫牠姊绘担瑙勩仧闁告鍘ч湁濡炲瀛╅~鏇㈡煙閹呮憼濠殿垱鎸抽弻娑樷攽閸℃浠鹃梺璇茬箰濡鈥旈崘顔嘉ч柛鈩兠喊宥夋⒑绾拋鍤嬬紒杈ㄦ礈閸掓帒顫濋懜鐢靛姸閻庡箍鍎遍幊鎰板礉閹绢喗鈷戦柛娑橈工婢瑰啴鏌涘☉鍗炵伇婵? console.log('=== WebRTC Statistics ==='); console.log(`Network Quality: ${statsLog.networkQuality}`); console.log('Video Stats:', statsLog.video); console.log('Audio Stats:', statsLog.audio); console.log('========================'); - - } catch (error) { + } + catch (error) { console.error('Error showing stats message:', error); } - }, 5000); // 濠?缂傚倸鍊搁崐鎼佸磹妞嬪海鐭嗗〒姘e亾閽樻繈鏌熷▓鍨灍闁哄棙绮嶆穱濠囧Χ閸屾矮澹曢梺缁樻尪閸婃繈寮诲☉婊庢Ъ濡炪們鍔岄幊搴ょ亱濠碘槅鍨紞宥呪槈濞嗘瑧鐭楁繛杈剧秬濡嫰宕㈤垾鎰佹富闁靛牆鎳愮粻鍝勵渻閺夋垶鎲搁柟骞垮灩閳藉濮€閻樿尪鈧灝顪冮妶鍡樺暗濠殿喚鍏橀弫宥夋偄閾忓湱锛濇繛鎾磋壘濞层倝寮搁敂鐣岀闁告粌鍟扮粔顔筋殽閻愯尙绠抽柍褜鍓ㄧ紞鍡涘窗閺嶎厽鍊堕柟鎯板Г椤ュ﹥銇勯幇闈涗簻妤犵偞顭囩槐鎺楁偐閸愭彃鎽靛┑? + }, 5000); } - - // 濠电姷鏁告慨鐑藉极閹间礁纾婚柣鎰惈缁犱即鏌熼梻瀵割槮缂佺姷濮垫穱濠囶敍濠靛嫧鍋撻埀顒勬煛鐎n亞效妤犵偞鐗楀蹇涘礈瑜庨崑褏绱掗悙顒€鍔ら柕鍫熸倐瀵鏁愭径濠勭潉闂侀€炲苯澧い顏勫暣瀹曠螖閳ь剛鎲撮敃鍌涚厓鐟滄粓宕滃杈ㄥ床婵炴垯鍨洪弲鏌ユ煕濞戝崬鏋ゆい锕備憾濮婃椽宕ㄦ繝鍐ㄩ瀺缂備浇顕ч崯浼村焵? clearStatsMessage() { console.log('Clearing stats message'); - - // 濠电姷鏁告慨鐑藉极閹间礁纾婚柣鎰惈缁犱即鏌熼梻瀵割槮缂佺姷濞€閺岀喖鎮ч崼鐔哄嚒缂備胶濮甸悧鏇㈠煘閹达附鍋愰柟缁樺俯娴尖偓缂備胶鍋撻崕鎶藉Χ閹间礁钃熼柨婵嗩槸缁秹鏌嶈閸撴瑩鈥旈崘顔奸敜婵°倐鍋撻悷娆欑畵閺屽秷顧侀柛鎾存皑閹广垹鈽夐姀鈩冩珳闂佸憡渚楅崰姘端囬柆宥嗏拺闁告稑锕ョ亸鎵磼鐠囨彃鏆熼柍褜鍓熷褔濡剁粙璺ㄦ殾闁靛ň鏅╅弫宥嗕繆閵堝倸浜炬繛瀛樼矒缁犳牠骞冨畡鎵虫瀻闊洦鎼╂导鈧俊鐐€х徊钘夘嚕閼哥數鈹嶅┑鐘叉处閸婄兘鏌熺紒妯哄潑闁稿鎹囧畷褰掝敊閻愵剚顔? if (this.statsInterval) { clearInterval(this.statsInterval); this.statsInterval = null; } } - - // Getters getState() { return this.state; } getLocalUser() { return this.state.session.localUser; } getRemoteUser() { return this.state.session.remoteUser; } - getConnectionId() { return this.connectionId; } - getRenderStreaming() { return this.renderstreaming; } } - -// 闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹濠德板€曢崯浼存儗濞嗘挻鐓欓悗鐢殿焾鍟哥紒鎯у綖缁瑩寮婚悢鐓庣闁逛即娼у▓顓犵磼缂併垹骞愰柛瀣崌濮婄粯鎷呴崨濠冨創濠电偠顕滅粻鎴︼綖濠靛惟闁挎洍鍋撴い鏇憾閺岀喖骞嶉纰辨毉闂佺锕﹂弫濠氬蓟濞戙垺鏅滈悹鍥ㄥ絻缁犺绻涚€电校閻㈩垳鍋熷Σ? const store = new CallStateManager(); - -// 婵犵數濮烽。顔炬閺囥垹纾婚柟杈剧畱绾捐淇婇妶鍛櫣闁哄绶氶幃褰掑炊瑜庨埢鏇㈡煟閹烘洘顥夐棁澶愭煕韫囨挸鎮戠紓宥嗗灩閻ヮ亪顢樺☉妯瑰婵犵數濮甸鏍窗濡ゅ啯宕查柛宀€鍋涚€氬銇勯幒鎴濃偓濠氭儗閸緷褰掓偂鎼达絾鎲奸梺缁樻尰濞茬喖寮婚悢鐓庣畾鐟滃繘鏁嶅澶婂唨闁挎稑瀚壕钘壝归敐鍫綈闁绘挶鍎查妵鍕敇閻樻彃骞嬪Δ? window.addEventListener('beforeunload', async () => { if (!store.renderstreaming) return; - await store.renderstreaming.stop(); // 闂傚倸鍊搁崐鐑芥嚄閸洍鈧箓宕奸妷顔芥櫈闂佺硶鍓濋悷銉╁垂濠靛牃鍋撻獮鍨姎妞わ缚绮欏顐﹀幢濡偐顔曢柣蹇曞仩婵倝顢撻崫绛奣C闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濋棃娑卞剰缁炬儳顭烽弻锝夊箛椤掑倷绮甸梺? + await store.renderstreaming.stop(); }, true); export default store; diff --git a/client/public/store.js.tmp b/client/public/store.js.tmp new file mode 100644 index 0000000..dccd7e8 --- /dev/null +++ b/client/public/store.js.tmp @@ -0,0 +1,1013 @@ +import { mockCallSession } from './models.js'; +import { RenderStreaming } from "../../module/renderstreaming.js"; +import { getServerConfig, getRTCConfiguration } from "../js/config.js"; +import { showNotification, generateId } from './utils.js'; +import chatMessage from './chatmessage.js'; +import { DEFAULT_PARTICIPANT_AVATAR, DEFAULT_PARTICIPANT_NAME, buildParticipantsSyncData, omitParticipant, removeParticipant, upsertParticipant } from './participants.js'; +import { AUDIO_CONFIG, VAD_CONFIG, VIDEO_ONLY_CONSTRAINT, buildVideoConstraints, getAdaptiveVideoBitrate, getResolutionLabel, getTargetResolutionBitrate } from './media-config.js'; +import { buildStatsLogPayload, createAudioAnalyser, getAudioLevel } from './media-monitoring.js'; +import { bindInviteSocketEvents, buildSocketUserInfoPayload, createSignalingInstance, ensureSignalingStarted, getActiveSignalingInstance, sendInviteSignal, sendSocketUserInfo } from './signaling-session.js'; +import { getNetworkQualityFromSummary, summarizeInboundStats } from './webrtc-stats.js'; +class CallStateManager { + constructor() { + this.state = { + id: generateId(), + session: { + ...mockCallSession, + status: 'idle' + }, + localStream: null, + remoteStream: null, + remoteStreams: {}, + participants: {} + }; + this.listeners = []; + this.socketEventHandlers = {}; + this._inviteEventSignaling = null; + } + subscribe(callback) { + this.listeners.push(callback); + return () => { + this.listeners = this.listeners.filter(cb => cb !== callback); + }; + } + notify(changes) { + this.listeners.forEach(cb => cb(this.state, changes)); + } + async init() { + await this.setupConfig(); + this.loadUserSettings(); + await this.getLocalStream(); + } + loadUserSettings() { + const userSettings = localStorage.getItem('userSettings'); + if (userSettings) { + try { + const settings = JSON.parse(userSettings); + if (settings.name || settings.avatar) { + this.state.session.localUser = { + ...this.state.session.localUser, + id: settings.userId || this.state.session.localUser.id, + name: settings.name || this.state.session.localUser.name, + avatar: settings.avatar || this.state.session.localUser.avatar + }; + this.notify({ type: 'USER_SETTINGS_UPDATED', user: this.state.session.localUser }); + } + if (settings.resolution) { + this._savedResolution = settings.resolution; + console.log(`闂傚倷娴囬褎顨ョ粙鍖¤€块梺顒€绉埀顒婄畵瀹曠厧鈹戦幇顒侇吙闂備胶鍘ч幗婊堝极閹间降鈧懘鏌ㄧ€c劋绨婚梺鍝勫暙濞诧箓藟婢跺瞼纾奸柛鎾茬娴犙囨煃瑜滈崜娆戠不瀹ュ纾块梺顒€绉寸粻鐘诲箹濞n剙濡介柛濠囨涧閳规垿鎮╃€圭姴顥濈紓浣哄珡閸ャ劎鍘卞銈嗗姧缁插潡鍩ユ径濞炬斀闂勫洤鈻旈弴銏犵劦妞ゆ帒鍠氬鎰版煟閳╁啯绀嬬€规洘鍨块獮鍥级鐠侯煈鍞甸梺璇插嚱缂嶅棝宕伴弽顐ょ焼闁割偁鍨洪崰鎰扮叓閸ャ劎鈽夐柛? ${settings.resolution.width}x${settings.resolution.height}`); + } + } + catch (error) { + console.error('Error loading user settings:', error); + } + } + } + async setupConfig() { + const res = await getServerConfig(); + this.useWebSocket = res.useWebSocket; + } + async getLocalStream() { + try { + console.log('Requesting camera permission...'); + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { + console.error('getUserMedia is not supported'); + throw new Error('getUserMedia is not supported'); + } + const videoConstraints = buildVideoConstraints(this._savedResolution); + const stream = await navigator.mediaDevices.getUserMedia({ + video: videoConstraints, + audio: AUDIO_CONFIG + }); + console.log('Stream obtained successfully:', stream); + console.log('Video tracks:', stream.getVideoTracks()); + console.log('Audio tracks:', stream.getAudioTracks()); + this.state.localStream = stream; + this.state.session.localUser.mediaState.video = true; + this.state.session.localUser.mediaState.audio = true; + console.log('Local stream stored, notifying UI...'); + this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream }); + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: true }); + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: true }); + this.emitMediaStateChange(); + this.startActivityDetection(this.state.localStream, { isLocal: true }); + } + catch (error) { + console.error('Error getting local stream:', error); + this.state.session.localUser.mediaState.video = false; + this.state.session.localUser.mediaState.audio = false; + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: false }); + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: false }); + } + } + async updateLocalMedia(mediaType, value) { + await this._updateLocalMediaRefactored(mediaType, value); + return; + } + async _updateLocalMediaRefactored(mediaType, value) { + if (mediaType === 'video' && value) { + await this._enableLocalVideo(); + this._notifyUserListUpdate(); + return; + } + this.state.session.localUser.mediaState[mediaType] = value; + this._notifyLocalMediaChange(mediaType, value); + this.emitMediaStateChange(); + if (mediaType === 'video' && !value) { + this._disableLocalVideoTracks(); + } + if (mediaType === 'audio') { + this._setLocalAudioTrackEnabled(value); + } + this._notifyUserListUpdate(); + } + async _enableLocalVideo() { + try { + const newVideoTrack = await this._requestNewVideoTrack(); + this._replaceLocalVideoTrack(newVideoTrack); + await this._updateOutgoingVideoTrack(newVideoTrack); + this.state.session.localUser.mediaState.video = true; + this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream: this.state.localStream }); + this._notifyLocalMediaChange('video', true); + this.emitMediaStateChange(); + this.startActivityDetection(this.state.localStream, { isLocal: true }); + } + catch (error) { + console.error('Error reopening video:', error); + this.state.session.localUser.mediaState.video = false; + this._notifyLocalMediaChange('video', false); + } + } + async _requestNewVideoTrack() { + const newVideoStream = await navigator.mediaDevices.getUserMedia(VIDEO_ONLY_CONSTRAINT); + const newVideoTrack = newVideoStream.getVideoTracks()[0]; + if (!newVideoTrack) { + throw new Error('Failed to get video track'); + } + return newVideoTrack; + } + _replaceLocalVideoTrack(newVideoTrack) { + if (this.state.localStream) { + const oldVideoTracks = this.state.localStream.getVideoTracks(); + oldVideoTracks.forEach(track => { + track.stop(); + this.state.localStream.removeTrack(track); + }); + this.state.localStream.addTrack(newVideoTrack); + return; + } + this.state.localStream = new MediaStream([newVideoTrack]); + } + async _updateOutgoingVideoTrack(newVideoTrack) { + if (!this.renderstreaming) { + return; + } + console.log('Updating video track in WebRTC connection'); + if (this.role === 'host') { + const participantIds = Object.keys(this.state.remoteStreams); + for (const participantId of participantIds) { + await this._updateVideoTrackForPeer(newVideoTrack, participantId); + } + return; + } + await this._updateVideoTrackForPeer(newVideoTrack); + } + async _updateVideoTrackForPeer(newVideoTrack, participantId = undefined) { + const transceivers = this.renderstreaming.getTransceivers(participantId); + if (!transceivers) { + return; + } + const videoTransceivers = transceivers.filter(transceiver => transceiver.sender && transceiver.sender.track && transceiver.sender.track.kind === 'video'); + if (videoTransceivers.length > 0) { + await this._replaceVideoTrackOnTransceivers(videoTransceivers, newVideoTrack, participantId); + } + else { + this._addVideoTransceiver(newVideoTrack, participantId); + } + this._scheduleVideoSenderUpdate(participantId); + } + async _replaceVideoTrackOnTransceivers(videoTransceivers, newVideoTrack, participantId) { + for (const transceiver of videoTransceivers) { + try { + await transceiver.sender.replaceTrack(newVideoTrack); + console.log(participantId + ? `Replaced video track for participant ${participantId}` + : 'Successfully replaced video track'); + } + catch (error) { + console.error(participantId + ? `Error replacing video track for ${participantId}:` + : 'Error replacing video track:', error); + } + } + } + _addVideoTransceiver(newVideoTrack, participantId) { + try { + if (participantId) { + this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' }, participantId); + console.log(`Added new video transceiver for participant ${participantId}`); + return; + } + this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' }); + console.log('Added new video transceiver'); + } + catch (error) { + console.error(participantId + ? `Error adding video transceiver for ${participantId}:` + : 'Error adding video transceiver:', error); + } + } + _scheduleVideoSenderUpdate(participantId) { + setTimeout(() => { this.setCodecPreferences(participantId); }, 100); + setTimeout(() => { this.setVideoEncodingParameters(participantId); }, 200); + } + _disableLocalVideoTracks() { + if (!this.state.localStream) { + return; + } + this.state.session.localUser.mediaState.video = false; + this.state.localStream.getTracks().forEach(track => { + if (track.kind === 'video') { + track.stop(); + } + }); + } + _setLocalAudioTrackEnabled(value) { + if (!this.state.localStream) { + return; + } + this.state.session.localUser.mediaState.audio = value; + this.state.localStream.getTracks().forEach(track => { + if (track.kind === 'audio') { + track.enabled = value; + } + }); + } + _notifyLocalMediaChange(mediaType, value) { + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType, value }); + } + _notifyUserListUpdate() { + this.notify({ + type: 'USER_LIST_UPDATE', + localUser: this.state.session.localUser, + remoteUser: this.state.session.remoteUser + }); + } + onSocketEvent(eventName, handler) { + this.socketEventHandlers[eventName] = handler; + } + async connectSignaling() { + await this.setupConfig(); + const { signaling, reused } = await ensureSignalingStarted(this._signaling, this.useWebSocket); + this._signaling = signaling; + this._inviteEventSignaling = bindInviteSocketEvents(this._signaling, this.socketEventHandlers, this._inviteEventSignaling); + if (reused) { + console.log('Signaling already connected, reusing existing instance'); + return this._signaling; + } + console.log('Signaling connected (WebSocket only, no room yet)'); + return this._signaling; + } + getActiveSignaling() { + return getActiveSignalingInstance(this._signaling, this.renderstreaming); + } + sendInviteCall(payload) { + sendInviteSignal(this.getActiveSignaling(), 'sendInviteCall', payload); + } + sendInviteAccepted(payload) { + sendInviteSignal(this.getActiveSignaling(), 'sendInviteAccepted', payload); + } + sendInviteRejected(payload) { + sendInviteSignal(this.getActiveSignaling(), 'sendInviteRejected', payload); + } + syncSocketUserInfo(userInfo = null) { + const payload = buildSocketUserInfoPayload(userInfo, this.state.session.localUser); + this.state.session.localUser = { + ...this.state.session.localUser, + id: payload.id, + name: payload.name, + avatar: payload.avatar + }; + sendSocketUserInfo(this.getActiveSignaling(), payload); + } + async _createSignalingAndRTC(connectionId) { + this.connectionId = connectionId; + this.state.session.status = 'connecting'; + this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); + if (!this.state.localStream) { + console.log('Local stream not available, waiting for initialization...'); + await new Promise((resolve) => { + const checkStream = () => { + if (this.state.localStream) { + resolve(); + } + else { + setTimeout(checkStream, 100); + } + }; + checkStream(); + }); + } + const signaling = this._signaling || createSignalingInstance(this.useWebSocket); + const config = getRTCConfiguration(); + this.renderstreaming = new RenderStreaming(signaling, config); + this._signaling = null; + } + async setUp(connectionId) { + await this._createSignalingAndRTC(connectionId); + this._registerCallbacks(); + await this._startConnection(connectionId); + } + _registerCallbacks() { + this.renderstreaming.onNewPeer = (participantId) => { + console.log(`New peer created for ${participantId}, adding local tracks`); + if (this.state.localStream) { + const tracks = this.state.localStream.getTracks(); + for (const track of tracks) { + this.renderstreaming.addTransceiver(track, { direction: 'sendonly' }, participantId); + } + this.setCodecPreferences(participantId); + this.setVideoEncodingParameters(participantId); + } + }; + this.renderstreaming.onConnect = (connectionId, data) => { + if (data && data.role) { + this.role = data.role; + this.state.session.localUser.isHost = (this.role === 'host'); + if (data.participantId) { + this.selfParticipantId = data.participantId; + } + console.log(`Connected as ${this.role}, participantId: ${this.selfParticipantId}`); + } + this.state.session.status = 'ongoing'; + this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' }); + if (this.role === 'participant') { + if (this.state.localStream) { + this.state.localStream.getAudioTracks().forEach(track => { + track.enabled = false; + }); + } + this.state.session.localUser.mediaState.audio = false; + this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: false }); + console.log('Participant joined with audio muted by default'); + } + this.sendMessage('user-info', { + id: this.state.session.localUser.id, + name: this.state.session.localUser.name, + avatar: this.state.session.localUser.avatar + }); + this.emitMediaStateChange(); + if (this.state.localStream) { + this.showStatsMessage(); + } + else { + console.error('Local stream is not available'); + showNotification('Local video stream is not available', 'error'); + } + }; + this.renderstreaming.onDisconnect = () => { + console.log('Received disconnect from server, host left or room closed'); + this.hangUp(); + }; + this.renderstreaming.onGotAnswer = (connectionId) => { + console.log('SDP Answer received, resetting encoding parameters for connectionId:', connectionId); + if (this.role === 'host') { + const allParticipantIds = Object.keys(this.state.remoteStreams || {}); + for (const pid of allParticipantIds) { + setTimeout(() => { this.setVideoEncodingParameters(pid); }, 50); + } + } + else { + setTimeout(() => { this.setVideoEncodingParameters(); }, 50); + } + }; + this.renderstreaming.onParticipantJoined = (participantId) => { + console.log(`Participant joined: ${participantId}`); + this._upsertParticipant(participantId); + this._notifyParticipantsUpdate(); + this.broadcastParticipantsList(); + }; + this.renderstreaming.onParticipantLeft = (participantId) => { + console.log(`Participant left: ${participantId}, room still active`); + this.updateRemoteUserStatus('offline'); + this.updateRemoteUserNetworkQuality('no_signal'); + showNotification('The other participant left the call', 'warning'); + if (this.state.remoteStreams[participantId]) { + this.state.remoteStreams[participantId].getTracks().forEach(track => track.stop()); + delete this.state.remoteStreams[participantId]; + } + if (this.state.remoteStream) { + this.state.remoteStream.getTracks().forEach(track => track.stop()); + this.state.remoteStream = null; + } + this._removeParticipant(participantId); + this.notify({ type: 'PARTICIPANT_LEFT', connectionId: participantId }); + this._notifyParticipantsUpdate(); + this.broadcastParticipantsList(); + }; + this.renderstreaming.onTrackEvent = (data) => { + this._handleTrackEvent(data); + }; + this.renderstreaming.onMessage = (data) => { + this._handleRenderStreamingMessage(data); + }; + } + async _startConnection(connectionId) { + await this.renderstreaming.start(); + await this.renderstreaming.createConnection(connectionId); + this.startNetworkQualityDetection(); + this.startActivityDetection(this.state.localStream, { isLocal: true }); + this.startActivityDetection(this.state.remoteStream, { isLocal: false }); + } + async hangUp() { + this.clearStatsMessage(); + this.stopNetworkQualityDetection(); + if (this.durationInterval) { + clearInterval(this.durationInterval); + this.durationInterval = null; + } + this.durationSynced = false; + const isHost = this.role === 'host'; + console.log(`Disconnect peer on ${this.connectionId}. Role: ${this.role}`); + if (this.renderstreaming) { + try { + await this.renderstreaming.deleteConnection(); + await this.renderstreaming.stop(); + } + catch (error) { + console.error('Error during hangUp:', error); + } + this.renderstreaming = null; + } + this.updateRemoteUserStatus('offline'); + this.updateRemoteUserNetworkQuality('no_signal'); + this.state.participants = {}; + this.selfParticipantId = null; + this.connectionId = null; + this.role = null; + this.state.session.status = 'ended'; + this.notify({ type: 'CALL_ENDED', reason: isHost ? 'host_hangup' : 'participant_hangup' }); + } + _handleTrackEvent(data) { + const direction = data.transceiver.direction; + if (direction === 'sendrecv' || direction === 'recvonly') { + this._handleIncomingTrack(data); + return; + } + if (direction === 'sendonly' && data.track.kind === 'audio') { + this.startActivityDetection(this.state.localStream, { isLocal: true }); + } + } + _handleIncomingTrack(data) { + const trackParticipantId = data.participantId || this.connectionId; + const isHost = this.role === 'host'; + const targetStream = this._getOrCreateRemoteStream(trackParticipantId, isHost); + this._replaceTrackOfSameKind(targetStream, data.track); + console.log('Added new track:', data.track.kind, 'for participant:', trackParticipantId); + if (isHost && !this.state.participants[trackParticipantId]) { + this._upsertParticipant(trackParticipantId); + this._notifyParticipantsUpdate(); + this.broadcastParticipantsList(); + } + this._notifyRemoteStreamUpdate(targetStream, trackParticipantId, isHost, data.track.kind); + if (this.state.session.remoteUser.status !== 'online') { + this.updateRemoteUserStatus('online'); + this.updateRemoteUserNetworkQuality('good'); + this.sendMessage('user-info', { + id: this.state.session.localUser.id, + name: this.state.session.localUser.name, + avatar: this.state.session.localUser.avatar + }); + this._startDurationTimer(); + } + if (data.track.kind === 'audio') { + this.startActivityDetection(this.state.remoteStream, { isLocal: false }); + } + } + _getOrCreateRemoteStream(trackParticipantId, isHost) { + if (isHost) { + if (!this.state.remoteStreams[trackParticipantId]) { + this.state.remoteStreams[trackParticipantId] = new MediaStream(); + } + return this.state.remoteStreams[trackParticipantId]; + } + if (this.state.remoteStream == null) { + this.state.remoteStream = new MediaStream(); + } + return this.state.remoteStream; + } + _replaceTrackOfSameKind(targetStream, track) { + const existingTracks = targetStream.getTracks().filter(existingTrack => existingTrack.kind === track.kind); + existingTracks.forEach(existingTrack => { + targetStream.removeTrack(existingTrack); + console.log('Removed old track:', existingTrack.kind); + }); + targetStream.addTrack(track); + } + _notifyRemoteStreamUpdate(targetStream, trackParticipantId, isHost, trackKind) { + const notifyStreamUpdate = () => { + this.notify({ + type: 'REMOTE_STREAM_OBTAINED', + stream: targetStream, + connectionId: trackParticipantId, + isHost + }); + console.log('Notified UI about remote stream update'); + }; + if (trackKind === 'audio' && targetStream.getVideoTracks().length === 0) { + console.log('Audio track arrived first, delaying stream notification for video track...'); + setTimeout(() => { + const nowHasVideo = targetStream.getVideoTracks().length > 0; + console.log(`After delay, stream has video: ${nowHasVideo}`); + notifyStreamUpdate(); + }, 200); + return; + } + notifyStreamUpdate(); + } + _handleRenderStreamingMessage(data) { + console.log('闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁革富鍘藉畷鍙夌箾閹存瑥鐏╃紒鐙呯稻缁绘繈妫冨☉姘暫濡炪們鍊愰崑鎾寸節?', data); + switch (data.type) { + case 'chat-message': + this._handleChatMessage(data); + break; + case 'media-state-changed': + this._handleMediaStateChangedMessage(data); + break; + case 'user-info': + this._handleUserInfoMessage(data); + break; + case 'participants-sync': + this._handleParticipantsSyncMessage(data); + break; + default: + break; + } + } + _handleChatMessage(data) { + const chatPayload = data.data || data.message; + if (!chatPayload) { + return; + } + chatMessage.handleChatMessage(chatPayload); + if (data.participantId && this.role === 'host' && this.state.participants[data.participantId]) { + this._upsertParticipant(data.participantId, { + id: chatPayload.senderId, + ...(chatPayload.senderName ? { name: chatPayload.senderName } : {}), + ...(chatPayload.senderAvatar ? { avatar: chatPayload.senderAvatar } : {}) + }); + this._notifyParticipantsUpdate(); + this.broadcastParticipantsList(); + return; + } + if (!this.role || this.role !== 'host') { + if (data.participantId && this.state.participants[data.participantId]) { + this._upsertParticipant(data.participantId, { + ...(chatPayload.senderName ? { name: chatPayload.senderName } : {}), + ...(chatPayload.senderAvatar ? { avatar: chatPayload.senderAvatar } : {}) + }); + this._notifyParticipantsUpdate(); + } + else if (chatPayload.senderId !== this.state.session.localUser.id) { + this._updateRemoteUserProfile({ + id: chatPayload.senderId, + name: chatPayload.senderName, + avatar: chatPayload.senderAvatar + }); + } + } + } + _handleMediaStateChangedMessage(data) { + console.log('闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁归棿绀侀悡姗€鏌熸潏鎯х槣闁轰礁绉电换娑㈠箣閻戝棛鍔烽梺鑽ゅ枑閸旀妲愰幘璇茬<婵ɑ鐦烽姀掳浜滈柟瀛樼箥濡偓濡ょ姷鍋涢崯鎾春閿熺姴宸濇い鏂垮悑閻ゅ倻绱撴担绋库挃濠⒀勵殜閺佸绻涚€涙鐭嬮柛搴㈠▕濠€渚€姊洪幐搴g畵婵炴潙鍊块幃鐐哄礈瑜忕壕鐣屸偓骞垮劚鐎氼喚绮i弮鍫熺厸?', data.data, 'from participant:', data.participantId); + if (this.role === 'host') { + if (data.participantId && this.state.participants[data.participantId]) { + this._upsertParticipant(data.participantId, { + mediaState: data.data + }); + } + this.updateRemoteMedia(data.data, data.participantId); + this._notifyParticipantsUpdate(); + this.broadcastParticipantsList(); + return; + } + if (data.participantId && data.participantId !== this.selfParticipantId && this.state.participants[data.participantId]) { + this._upsertParticipant(data.participantId, { + mediaState: data.data + }); + this._notifyParticipantsUpdate(); + return; + } + if (data.participantId === this.selfParticipantId) { + return; + } + console.log('Received media-state-changed from Host, updating remoteUser:', data.data); + this.updateRemoteMedia(data.data, data.participantId); + this._notifyParticipantsUpdate(); + } + _handleUserInfoMessage(data) { + console.log('闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁秆勵殔閽冪喖鏌ㄥ┑鍡╂缂傚秵鐗楅妵鍕箳閸℃ぞ澹曞┑鐘殿暯閳ь剝灏欓惌娆撴煛鐏炲墽娲撮柛鈺嬬節瀹曟帒鈹冮幆褜娼撶紓鍌氬€风粈渚€鎯岄崒娑氼洸闁割偅娲栭弰銉╂煕閺囥劌鐏犵紒鈧崘顏呭枑闊洦娲滈惌鍡涙煃?', data.data, 'from participant:', data.participantId); + if (!data.data) { + return; + } + if (data.participantId && this.role === 'host') { + this._upsertParticipant(data.participantId, { + id: data.data.id || '', + name: data.data.name || DEFAULT_PARTICIPANT_NAME, + avatar: data.data.avatar || DEFAULT_PARTICIPANT_AVATAR + }); + this._notifyParticipantsUpdate(); + this.broadcastParticipantsList(); + return; + } + this._updateRemoteUserProfile({ + id: data.data.id || this.state.session.remoteUser.id, + name: data.data.name || this.state.session.remoteUser.name, + avatar: data.data.avatar || this.state.session.remoteUser.avatar + }); + } + _handleParticipantsSyncMessage(data) { + if (this.role === 'host' || !data.data) { + return; + } + console.log('闂傚倸鍊搁崐宄懊归崶顒€违闁逞屽墴閺屾稓鈧綆鍋呭畷宀勬煙椤旇偐绉虹€规洦鍋婂畷鐔碱敆娴g澹嶉梻鍌欒兌缁垶骞愰幖浣哥9闁秆勵殔閽冪喖鏌i弮鍥モ偓鈧柛瀣尭閳藉鈻嶉褌绨奸柟渚垮姂瀹曞爼顢楁担鍝勫箥闂備礁鎲¢悷銉┧囬鐐茬厺闁哄洨濮崑鎾舵喆閸曨剛顦ㄩ梺鑹邦潐瀹曟﹢鎮橀崘顔解拺闁告稑锕ョ壕鐢告煛閸屾瑧绐旂€规洘鍨块獮妯兼嫚閼碱剦妲版俊鐐€栧Λ浣圭珶閸綆鏉洪梻鍌欐祰椤曆呮崲閹烘纾婚柣鏂垮悑閹偤骞栧ǎ顒€濡肩紒鈧?', data.data); + this.state.participants = omitParticipant(data.data, this.selfParticipantId); + this._notifyParticipantsUpdate(); + this._syncCallDuration(data.callDuration); + } + _updateRemoteUserProfile(profile) { + this._setRemoteUserState(profile); + this._notifyRemoteUserChange({ mediaState: this.state.session.remoteUser.mediaState }); + } + _syncCallDuration(callDuration) { + if (this.durationSynced || typeof callDuration !== 'number') { + return; + } + this.state.session.duration = callDuration; + this.durationSynced = true; + this._startDurationTimer(); + this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration }); + console.log(`Call duration synced: ${callDuration} seconds`); + } + _startDurationTimer() { + if (this.durationInterval) { + return; + } + this.durationInterval = setInterval(() => { + this.state.session.duration++; + this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration }); + }, 1000); + } + _setRemoteUserState(patch) { + this.state.session.remoteUser = { + ...this.state.session.remoteUser, + ...patch + }; + } + _setRemoteUserMediaState(mediaState) { + this._setRemoteUserState({ + mediaState: { + ...this.state.session.remoteUser.mediaState, + ...mediaState + } + }); + } + _notifyRemoteUserChange(changes = {}) { + this.notify({ + type: 'REMOTE_MEDIA_CHANGE', + ...changes, + localUser: this.state.session.localUser, + remoteUser: this.state.session.remoteUser + }); + this._notifyUserListUpdate(); + } + sendMessage(type, data) { + if (this.renderstreaming) { + this.renderstreaming.sendMessage({ + type: type, + data: data + }); + } + } + _notifyParticipantsUpdate() { + this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants }); + } + _upsertParticipant(participantId, patch = {}) { + return upsertParticipant(this.state.participants, participantId, patch); + } + _removeParticipant(participantId) { + return removeParticipant(this.state.participants, participantId); + } + broadcastParticipantsList() { + if (this.role !== 'host' || !this.renderstreaming) + return; + const memberList = buildParticipantsSyncData(this.state.session.localUser, this.state.participants); + this.renderstreaming.sendMessage({ + type: 'participants-sync', + data: memberList, + callDuration: this.state.session.duration + }); + console.log('Broadcast participants list:', Object.keys(memberList)); + } + setCodecPreferences(participantId) { + const capabilities = RTCRtpSender.getCapabilities('video'); + if (!capabilities || !capabilities.codecs || capabilities.codecs.length === 0) + return; + const { codecs } = capabilities; + let selectedCodecs = []; + const av1Codec = codecs.find(c => c.mimeType === 'video/AV1'); + const vp9Codec = codecs.find(c => c.mimeType === 'video/VP9'); + const h264HighCodec = codecs.find(c => c.mimeType === 'video/H264' && + c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=6400')); + const h264Codec = codecs.find(c => c.mimeType === 'video/H264'); + if (av1Codec) + selectedCodecs.push(av1Codec); + if (vp9Codec) + selectedCodecs.push(vp9Codec); + if (h264HighCodec) + selectedCodecs.push(h264HighCodec); + if (h264Codec && (!h264HighCodec || h264Codec !== h264HighCodec)) + selectedCodecs.push(h264Codec); + if (selectedCodecs.length === 0) + return; + if (this.renderstreaming) { + const transceivers = this.renderstreaming.getTransceivers(participantId); + if (transceivers && transceivers.length > 0) { + const videoTransceivers = transceivers.filter(t => { + if (t.sender && t.sender.track) { + return t.sender.track.kind === 'video'; + } + return t.mid !== null && t.receiver && t.receiver.track && t.receiver.track.kind === 'video'; + }); + if (videoTransceivers && videoTransceivers.length > 0) { + videoTransceivers.forEach(t => { + try { + t.setCodecPreferences(selectedCodecs); + } + catch (e) { + console.error('Error setting codec preferences:', e); + } + }); + console.log(`Codec preferences set: ${selectedCodecs.map(c => c.mimeType).join(' > ')}`); + } + } + } + } + setVideoEncodingParameters(participantId) { + if (!this.renderstreaming) + return; + const transceivers = this.renderstreaming.getTransceivers(participantId); + if (!transceivers || transceivers.length === 0) + return; + const videoTransceivers = transceivers.filter(t => t.sender && t.sender.track && t.sender.track.kind === 'video'); + for (const transceiver of videoTransceivers) { + try { + const sender = transceiver.sender; + const params = sender.getParameters(); + if (!params.encodings || params.encodings.length === 0) { + params.encodings = [{}]; + } + const videoTrack = sender.track; + const settings = videoTrack ? videoTrack.getSettings() : {}; + const height = settings.height || 1080; + const maxBitrate = getAdaptiveVideoBitrate(height); + params.encodings[0].maxBitrate = maxBitrate; + params.encodings[0].scaleResolutionDownBy = 1.0; + params.encodings[0].xGoogleMinBitrate = Math.floor(maxBitrate * 0.5); + if (params.degradationPreference !== undefined) { + params.degradationPreference = 'maintain-resolution'; + } + sender.setParameters(params); + console.log(`Set video encoding: maxBitrate=${maxBitrate / 1000000}Mbps, scaleResolutionDownBy=1.0, xGoogleMinBitrate=${Math.floor(maxBitrate * 0.5)}${participantId ? ` for ${participantId}` : ''}`); + } + catch (error) { + console.error('Error setting video encoding parameters:', error); + } + } + } + async changeResolution(width, height) { + if (!this.state.localStream) { + showNotification('Local video stream is not available', 'error'); + return; + } + const videoTracks = this.state.localStream.getVideoTracks(); + if (videoTracks.length === 0) { + showNotification('Failed to switch resolution', 'error'); + return; + } + const track = videoTracks[0]; + const label = getResolutionLabel(height); + try { + await track.applyConstraints({ + width: { ideal: width, max: width }, + height: { ideal: height, max: height }, + frameRate: { ideal: 30, max: 30 } + }); + console.log(`闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹闂佸湱鍎ら崵锕€鈽夊鍡欏弳闂佸憡娲嶉弲娆戝垝閹剧粯鈷戠憸鐗堝笚閿涚喖鏌i幒鐐电暤鐎规洘鍨甸埞鎴犫偓锝庡亞閸橀亶姊洪棃娑辨Ч闁搞劎鏁诲畷顖烆敃閳垛晜鐏侀梺闈涱槴閺呮粓鍩涢幋锔界厱婵炴垶锕銉х磼濡や礁绗氱紒缁樼〒閹风姾顦撮柣锝変憾閺岋綁鏁愰崶褍骞嬮悗瑙勬穿缁叉儳顕ラ崟顓濇勃闁告稑锕︽禍?${width}x${height}`); + const maxBitrate = getTargetResolutionBitrate(height); + this._applyMaxBitrate(maxBitrate); + const userSettings = JSON.parse(localStorage.getItem('userSettings') || '{}'); + userSettings.resolution = { width, height }; + localStorage.setItem('userSettings', JSON.stringify(userSettings)); + this.notify({ type: 'RESOLUTION_CHANGED', resolution: { width, height, label } }); + showNotification('Switched to ' + label, 'success'); + } + catch (error) { + console.error('闂傚倸鍊搁崐椋庣矆娓氣偓楠炲鏁嶉崟顒佹闂佸湱鍎ら崵姘洪鍛珖闂侀€炲苯澧撮柟顕€绠栭幃婊堟寠婢跺孩鎲伴梻渚€娼чˇ顓㈠磿閹跺壙鍥敃閿旇В鎷虹紓渚囧灡濞叉牗鏅堕懠顑藉亾閸忓浜鹃梺褰掓?缁€浣瑰閻樺磭绠剧€瑰壊鍠曠花濂告煟閹捐泛鏋戠紒缁樼箖缁绘繈宕掑顓燁唹闂備胶鎳撻崥瀣礉濞嗘挸钃熼柡鍥ュ灩閻愬﹪鏌曟繛鍨姢濞寸姴銈稿?', error); + showNotification('Failed to switch resolution. The camera may not support it.', 'error'); + } + } + _applyMaxBitrate(maxBitrate) { + if (!this.renderstreaming) + return; + const isHost = this.role === 'host'; + const participantIds = isHost ? Object.keys(this.state.participants) : [null]; + for (const pid of participantIds) { + const transceivers = this.renderstreaming.getTransceivers(pid); + if (!transceivers) + continue; + const videoTransceivers = transceivers.filter(t => t.sender && t.sender.track && t.sender.track.kind === 'video'); + for (const transceiver of videoTransceivers) { + try { + const sender = transceiver.sender; + const params = sender.getParameters(); + if (!params.encodings || params.encodings.length === 0) { + params.encodings = [{}]; + } + params.encodings[0].maxBitrate = maxBitrate; + sender.setParameters(params); + console.log(`Updated maxBitrate to ${maxBitrate} for ${pid || 'self'}`); + } + catch (error) { + console.error('Error updating maxBitrate:', error); + } + } + } + } + updateRemoteMedia(mediaState, participantId) { + this._setRemoteUserMediaState(mediaState); + this._notifyRemoteUserChange({ mediaState, participantId }); + } + updateRemoteUserStatus(status) { + this._setRemoteUserState({ status }); + this._notifyRemoteUserChange(); + } + updateRemoteUserNetworkQuality(networkQuality) { + this._setRemoteUserState({ networkQuality }); + this._notifyRemoteUserChange(); + } + _setSpeakingState(isLocal, isSpeaking) { + if (isLocal) { + this.state.session.localUser.mediaState.isSpeaking = isSpeaking; + this._notifyLocalMediaChange('isSpeaking', isSpeaking); + this.emitMediaStateChange(); + return; + } + this.updateRemoteMedia({ isSpeaking }); + } + async endCall() { + console.log(`endCall called. Role: ${this.role}`); + await this.hangUp(); + } + async joinCall(connectionId) { + this.state.session.status = 'connecting'; + this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); + showNotification('Joining call (' + connectionId + ')'); + await this.init(); + this.connectionId = connectionId; + } + async createCall() { + this.state.session.status = 'connecting'; + this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' }); + showNotification('Creating call...'); + await this.init(); + } + async detectNetworkQuality() { + if (!this.renderstreaming) { + return; + } + try { + const stats = await this.renderstreaming.getStats(); + if (!stats) { + return; + } + const summary = summarizeInboundStats(stats); + const quality = getNetworkQualityFromSummary(summary); + if (this.state.session.remoteUser.networkQuality !== quality) { + this.updateRemoteUserNetworkQuality(quality); + this.notify({ type: 'NETWORK_CHANGE', quality }); + } + } + catch (error) { + console.error('Error detecting network quality:', error); + } + } + startActivityDetection(stream, { isLocal = false } = {}) { + if (!stream) { + return; + } + const audioTracks = stream.getAudioTracks(); + if (audioTracks.length === 0) { + return; + } + try { + const { threshold, debounceTime, fftSize } = VAD_CONFIG; + const { analyser, dataArray } = createAudioAnalyser(stream, fftSize); + let isSpeaking = false; + let lastActivityTime = 0; + const detectActivity = () => { + if (!stream || !this.renderstreaming) { + return; + } + const level = getAudioLevel(analyser, dataArray); + const currentTime = Date.now(); + if (level > threshold / 100) { + lastActivityTime = currentTime; + if (!isSpeaking) { + isSpeaking = true; + this._setSpeakingState(isLocal, true); + } + } + else if (isSpeaking && currentTime - lastActivityTime > debounceTime) { + isSpeaking = false; + this._setSpeakingState(isLocal, false); + } + if (this.state.session.status === 'ongoing') { + requestAnimationFrame(detectActivity); + } + }; + detectActivity(); + console.log(`${isLocal ? 'Local' : 'Remote'} activity detection started`); + } + catch (error) { + console.error(`Error starting ${isLocal ? 'local' : 'remote'} activity detection:`, error); + } + } + startNetworkQualityDetection() { + this.networkQualityInterval = setInterval(() => { + this.detectNetworkQuality(); + }, 3000); + } + stopNetworkQualityDetection() { + if (this.networkQualityInterval) { + clearInterval(this.networkQualityInterval); + this.networkQualityInterval = null; + } + } + emitMediaStateChange() { + const payload = { + userId: this.state.session.localUser.id, + ...this.state.session.localUser.mediaState + }; + console.log('[WebSocket Emit] media-state-changed:', payload); + if (this.renderstreaming) { + this.renderstreaming.sendMessage({ + type: 'media-state-changed', + data: payload + }); + } + } + async showStatsMessage() { + console.log('Showing stats message'); + await this.detectNetworkQuality(); + this.statsInterval = setInterval(async () => { + if (!this.renderstreaming) { + return; + } + try { + const stats = await this.renderstreaming.getStats(); + if (!stats) { + return; + } + const statsSummary = summarizeInboundStats(stats); + const statsLog = buildStatsLogPayload(this.state.session.remoteUser.networkQuality, statsSummary); + console.log('=== WebRTC Statistics ==='); + console.log(`Network Quality: ${statsLog.networkQuality}`); + console.log('Video Stats:', statsLog.video); + console.log('Audio Stats:', statsLog.audio); + console.log('========================'); + } + catch (error) { + console.error('Error showing stats message:', error); + } + }, 5000); + } + clearStatsMessage() { + console.log('Clearing stats message'); + if (this.statsInterval) { + clearInterval(this.statsInterval); + this.statsInterval = null; + } + } + getState() { return this.state; } + getLocalUser() { return this.state.session.localUser; } + getRemoteUser() { return this.state.session.remoteUser; } + getConnectionId() { return this.connectionId; } + getRenderStreaming() { return this.renderstreaming; } +} +const store = new CallStateManager(); +window.addEventListener('beforeunload', async () => { + if (!store.renderstreaming) + return; + await store.renderstreaming.stop(); +}, true); +export default store;