diff --git a/plugin/live/amd/build/videotime.min.js b/plugin/live/amd/build/videotime.min.js index f12a8145..4cacc10f 100644 --- a/plugin/live/amd/build/videotime.min.js +++ b/plugin/live/amd/build/videotime.min.js @@ -6,6 +6,6 @@ define("videotimeplugin_live/videotime",["exports","core/ajax","mod_videotime/vi * @module videotimeplugin_live/videotime * @copyright 2022 bdecent gmbh * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_videotime=_interopRequireDefault(_videotime),_janusGateway=_interopRequireDefault(_janusGateway),_log=_interopRequireDefault(_log),_notification=_interopRequireDefault(_notification),_publish=_interopRequireDefault(_publish),_subscribe=_interopRequireDefault(_subscribe),_socket=_interopRequireDefault(_socket);var rooms={};class Publish extends _publish.default{register(pluginHandle){return _ajax.default.call([{args:{handle:pluginHandle.getId(),id:Number(this.contextid),plugin:pluginHandle.plugin,room:this.roomid,ptype:"publish"==this.ptype,session:pluginHandle.session.getSessionId()},contextid:this.contextid,fail:_notification.default.exception,methodname:"videotimeplugin_live_join_room"}])[0].then((response=>{this.feed=response.id})).catch(_notification.default.exception)}publishFeed(){return _ajax.default.call([{args:{id:Number(this.feed),room:this.roomid},contextid:this.contextid,fail:_notification.default.exception,methodname:"videotimeplugin_live_publish_feed"}])[0]}unpublish(){return _ajax.default.call([{args:{id:Number(this.feed),publish:!1,room:this.roomid},contextid:this.contextid,fail:_notification.default.exception,methodname:"videotimeplugin_live_publish_feed"}])[0]}handleClose(){document.querySelectorAll('[data-contextid="'+this.contextid+'"][data-action="publish"]').forEach((button=>{button.classList.remove("hidden")})),this.janus.destroy(),[this.currentCamera||Promise.resolve(null),this.currentDisplay||Promise.resolve(null)].forEach((videoInput=>{videoInput.then((videoStream=>(videoStream&&videoStream.getVideoTracks().forEach((track=>{track.enabled=!1,track.stop()})),null))).catch(_notification.default.exception)}))}onLocalTrack(track,on){const remoteStream=new MediaStream([track]);on&&(remoteStream.mid=track.mid,_log.default.debug(on),_log.default.debug(remoteStream),_janusGateway.default.attachMediaStream(document.getElementById("video-controls-"+this.tracks[track.id]),remoteStream))}handleClick(e){const button=e.target.closest('[data-contextid="'+this.contextid+'"][data-action="publish"], [data-contextid="'+this.contextid+'"][data-action="unpublish"]');if(button){const action=button.getAttribute("data-action"),type=button.getAttribute("data-type")||"camera";switch(e.stopPropagation(),e.preventDefault(),document.querySelectorAll('[data-region="deft-venue"] [data-action="publish"], [data-region="deft-venue"] [data-action="unpublish"]').forEach((button=>{button.getAttribute("data-action")==action&&button.getAttribute("data-type")==type||button.classList.remove("hidden")})),action){case"publish":_log.default.debug(type),"display"==type?this.shareDisplay():this.shareCamera(),this.videoInput.then((videoStream=>{const tracks=[];if(this.tracks=this.tracks||{},videoStream){const transceiver=this.getTransceiver();if(videoStream.getVideoTracks().forEach((track=>{if(track.addEventListener("ended",(()=>{this.selectedTrack!=track.id&&this.unpublish()})),transceiver)return this.videoroom.replaceTracks({tracks:[{type:"video",mid:transceiver.mid,capture:track}],error:_notification.default.exception}),void(this.selectedTrack=track);tracks.push({type:"video",capture:track,recv:!1}),this.selectedTrack=track})),!tracks.length)return videoStream;if(videoStream.getAudioTracks().forEach((track=>{tracks.push({type:"audio",capture:track,recv:!1})})),!tracks.length)return videoStream;this.videoroom.createOffer({tracks:tracks,success:jsep=>{this.videoroom.send({message:{request:"configure",video:!0,audio:!0},jsep:jsep})},error:function(error){_notification.default.alert("WebRTC error... ",error.message)}})}return videoStream})).catch(_notification.default.exception);break;case"unpublish":this.unpublish()}}return!0}shareCamera(){const videoInput=this.videoInput,currentCamera=this.currentCamera||Promise.resolve(null);this.videoInput=currentCamera.then((videoStream=>{if(videoStream)return videoStream;{const cameraInput=navigator.mediaDevices.getUserMedia({video:!0,audio:!1});return this.currentCamera=cameraInput.catch((()=>currentCamera)),cameraInput.then((videoStream=>(this.tracks=this.tracks||{},videoStream.getTracks().forEach((track=>{this.tracks[track.id]="camera"})),videoStream))).catch((e=>(_log.default.debug(e),videoInput)))}}))}shareDisplay(){const videoInput=this.videoInput,currentDisplay=this.currentDisplay||Promise.resolve(null),displayInput=navigator.mediaDevices.getDisplayMedia({video:!0,audio:!1});this.videoInput=displayInput.then((videoStream=>(videoInput&&videoInput.then((videoStream=>(videoStream&&videoStream.getTracks().forEach((track=>{_log.default.debug(track)})),videoStream))).catch(_notification.default.exception),this.tracks=this.tracks||{},videoStream.getTracks().forEach((track=>{this.tracks[track.id]="display"})),videoStream))).catch((e=>(_log.default.debug(e),videoInput))),this.currentDisplay=displayInput.then((videoStream=>(currentDisplay.then((videoStream=>{videoStream&&videoStream.getTracks().forEach((track=>{_log.default.debug("stop track"),_log.default.debug(track),track.stop()}))})),videoStream))).catch((e=>(_log.default.debug(e),currentDisplay)))}}class VideoTime extends _videotime.default{initialize(contextid,token,peerid){return _log.default.debug("Initializing Video Time "+this.elementId),this.contextid=contextid,this.peerid=peerid,_ajax.default.call([{methodname:"videotimeplugin_live_get_room",args:{contextid:contextid},done:response=>{const socket=new _socket.default(contextid,token);this.iceservers=JSON.parse(response.iceservers),this.roomid=response.roomid,this.server=response.server,rooms[String(contextid)]={contextid:contextid,peerid:peerid,roomid:response.roomid,server:response.server,iceServers:JSON.parse(response.iceservers)},this.roomid=response.roomid,socket.subscribe((()=>{_ajax.default.call([{methodname:"videotimeplugin_live_get_feed",args:{contextid:contextid},done:response=>{const room=rooms[String(contextid)];room.publish&&room.publish.restart&&(response.feed==peerid&&this.unpublish(),room.publish=null),this.subscribeTo(Number(response.feed))},fail:_notification.default.exception}])}))},fail:_notification.default.exception}]),this.addListeners(),!0}addListeners(){document.querySelector("body").removeEventListener("click",handleClick),document.querySelector("body").addEventListener("click",handleClick)}subscribeTo(source){if(document.querySelectorAll('[data-contextid="'+this.contextid+'"][data-action="publish"]').forEach((button=>{Number(this.peerid),button.classList.remove("hidden")})),document.querySelectorAll('[data-contextid="'+this.contextid+'"][data-action="unpublish"]').forEach((button=>{Number(this.peerid),button.classList.remove("hidden")})),_log.default.debug(source),!this.remoteFeed||this.remoteFeed.creatingSubscription||this.remoteFeed.restart)this.remoteFeed&&this.remoteFeed.restart?this.remoteFeed.current!=source&&(this.remoteFeed=null,this.subscribeTo(source)):this.remoteFeed?setTimeout((()=>{this.subscribeTo(source)}),500):source&&(this.remoteFeed=new Subscribe(this.contextid,this.iceservers,this.roomid,this.server,this.peerid,source),this.remoteFeed.remoteVideo=document.getElementById(this.elementId),this.remoteFeed.remoteAudio=document.getElementById(this.elementId).parentNode.querySelector("audio"),this.remoteFeed.startConnection(source),document.querySelectorAll('[data-contextid="'+this.contextid+'"] img.poster-img').forEach((img=>{img.classList.add("hidden")})),document.querySelectorAll('[data-contextid="'+this.contextid+'"] video').forEach((img=>{img.classList.remove("hidden")})));else{const update={request:"update",subscribe:[{feed:Number(source)}],unsubscribe:[{feed:Number(this.remoteFeed.current)}]};if(!source&&this.remoteFeed.current?delete update.subscribe:source&&!this.remoteFeed.current&&delete update.unsubscribe,this.remoteFeed.current!=source){const room=rooms[String(this.contextid)];this.remoteFeed.videoroom.send({message:update}),room.publish&&this.remoteFeed.current==room.publish.feed&&(room.publish.handleClose(),room.publish=null),this.remoteFeed.current=source,!source&&this.remoteFeed.current&&(this.remoteFeed.handleClose(),this.remoteFeed=null),_log.default.debug('[data-contextid="'+this.contextid+'"] img.poster-img'),Number(source)?(document.querySelectorAll('[data-contextid="'+this.contextid+'"] img.poster-img').forEach((img=>{img.classList.add("hidden")})),document.querySelectorAll('[data-contextid="'+this.contextid+'"] video').forEach((video=>{video.classList.remove("hidden")}))):(document.querySelectorAll('[data-contextid="'+this.contextid+'"] img.poster-img').forEach((img=>{img.classList.remove("hidden")})),document.querySelectorAll('[data-contextid="'+this.contextid+'"] video').forEach((video=>{video.classList.add("hidden")})))}}}}_exports.default=VideoTime;const handleClick=function(e){const button=e.target.closest('[data-roomid] [data-action="publish"], [data-roomid] [data-action="unpublish"]');if(button){const action=button.getAttribute("data-action"),contextid=e.target.closest("[data-contextid]").getAttribute("data-contextid"),room=rooms[String(contextid)],iceServers=room.iceServers,peerid=room.peerid,roomid=room.roomid,server=room.server,type=button.getAttribute("data-type");e.stopPropagation(),e.preventDefault(),"publish"!=action||room.publish&&!room.publish.restart?room.publish.handleClick(e):(room.publish=new Publish(contextid,iceServers,roomid,server,peerid),"display"==type?room.publish.shareDisplay():room.publish.shareCamera(),room.publish.startConnection())}};class Subscribe extends _subscribe.default{register(pluginHandle){return _ajax.default.call([{args:{handle:pluginHandle.getId(),id:Number(this.contextid),plugin:pluginHandle.plugin,room:this.roomid,ptype:!1,feed:this.feed,session:pluginHandle.session.getSessionId()},contextid:this.contextid,fail:_notification.default.exception,methodname:"videotimeplugin_live_join_room"}])[0]}}return _exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_videotime=_interopRequireDefault(_videotime),_janusGateway=_interopRequireDefault(_janusGateway),_log=_interopRequireDefault(_log),_notification=_interopRequireDefault(_notification),_publish=_interopRequireDefault(_publish),_subscribe=_interopRequireDefault(_subscribe),_socket=_interopRequireDefault(_socket);var rooms={};class Publish extends _publish.default{register(pluginHandle){return _ajax.default.call([{args:{handle:pluginHandle.getId(),id:Number(this.contextid),plugin:pluginHandle.plugin,room:this.roomid,ptype:"publish"==this.ptype,session:pluginHandle.session.getSessionId()},contextid:this.contextid,fail:_notification.default.exception,methodname:"videotimeplugin_live_join_room"}])[0].then((response=>{this.feed=response.id})).catch(_notification.default.exception)}publishFeed(){return _ajax.default.call([{args:{id:Number(this.feed),room:this.roomid},contextid:this.contextid,fail:_notification.default.exception,methodname:"videotimeplugin_live_publish_feed"}])[0]}unpublish(){return _ajax.default.call([{args:{id:Number(this.feed),publish:!1,room:this.roomid},contextid:this.contextid,fail:_notification.default.exception,methodname:"videotimeplugin_live_publish_feed"}])[0]}handleClose(){document.querySelectorAll('[data-contextid="'+this.contextid+'"][data-action="publish"]').forEach((button=>{button.classList.remove("hidden")})),this.janus.destroy(),[this.currentCamera||Promise.resolve(null),this.currentDisplay||Promise.resolve(null)].forEach((videoInput=>{videoInput.then((videoStream=>(videoStream&&videoStream.getVideoTracks().forEach((track=>{track.enabled=!1,track.stop()})),null))).catch(_notification.default.exception)}))}onLocalTrack(track,on){const remoteStream=new MediaStream([track]);on&&"audio"!=track.kind&&(remoteStream.mid=track.mid,_log.default.debug(on),_log.default.debug(remoteStream),_janusGateway.default.attachMediaStream(document.getElementById("video-controls-"+this.tracks[track.id]),remoteStream))}handleClick(e){const button=e.target.closest('[data-contextid="'+this.contextid+'"][data-action="publish"], [data-contextid="'+this.contextid+'"][data-action="mute"], [data-contextid="'+this.contextid+'"][data-action="unmute"], [data-contextid="'+this.contextid+'"][data-action="unpublish"]');if(button){const action=button.getAttribute("data-action"),type=button.getAttribute("data-type")||"camera",transceiver=this.getTransceiver("audio");switch(e.stopPropagation(),e.preventDefault(),document.querySelectorAll('[data-region="deft-venue"] [data-action="publish"], [data-region="deft-venue"] [data-action="unpublish"]').forEach((button=>{button.getAttribute("data-action")==action&&button.getAttribute("data-type")==type||button.classList.remove("hidden")})),action){case"mute":transceiver&&(transceiver.sender.track.enabled=!1);break;case"unmute":transceiver&&(transceiver.sender.track.enabled=!0);break;case"publish":_log.default.debug(type),"display"==type?this.shareDisplay():this.shareCamera(),this.processStream([]);break;case"unpublish":this.unpublish()}}return!0}shareCamera(){const videoInput=this.videoInput,currentCamera=this.currentCamera||Promise.resolve(null);this.videoInput=currentCamera.then((videoStream=>{if(videoStream)return videoStream;{const cameraInput=navigator.mediaDevices.getUserMedia({video:!0,audio:!0});return this.currentCamera=cameraInput.catch((()=>currentCamera)),cameraInput.then((videoStream=>(this.tracks=this.tracks||{},videoStream.getTracks().forEach((track=>{this.tracks[track.id]="camera"})),videoStream))).catch((e=>(_log.default.debug(e),videoInput)))}}))}shareDisplay(){const videoInput=this.videoInput,currentDisplay=this.currentDisplay||Promise.resolve(null),displayInput=navigator.mediaDevices.getDisplayMedia({video:!0,audio:!0});this.videoInput=displayInput.then((videoStream=>(videoInput&&videoInput.then((videoStream=>(videoStream&&videoStream.getTracks().forEach((track=>{_log.default.debug(track)})),videoStream))).catch(_notification.default.exception),this.tracks=this.tracks||{},videoStream.getTracks().forEach((track=>{this.tracks[track.id]="display"})),videoStream))).catch((e=>(_log.default.debug(e),videoInput))),this.currentDisplay=displayInput.then((videoStream=>(currentDisplay.then((videoStream=>{videoStream&&videoStream.getTracks().forEach((track=>{_log.default.debug("stop track"),_log.default.debug(track),track.stop()}))})),videoStream))).catch((e=>(_log.default.debug(e),currentDisplay)))}processStream(tracks){this.videoInput.then((videoStream=>{if(this.tracks=this.tracks||{},videoStream){const audiotransceiver=this.getTransceiver("audio"),videotransceiver=this.getTransceiver("video");if(videoStream.getVideoTracks().forEach((track=>{track.addEventListener("ended",(()=>{this.selectedTrack!=track.id&&this.unpublish()})),this.selectedTrack=track,videotransceiver?this.videoroom.replaceTracks({tracks:[{type:"video",mid:videotransceiver.mid,capture:track}],error:_notification.default.exception}):tracks.push({type:"video",capture:track,recv:!1})})),videoStream.getAudioTracks().forEach((track=>{track.addEventListener("ended",(()=>{this.selectedTrack!=track.id&&this.unpublish()})),document.querySelector('.hidden[data-action="mute"][data-contextid="'+this.contextid+'"]')&&(track.enabled=!1),audiotransceiver?this.videoroom.replaceTracks({tracks:[{type:"audio",mid:audiotransceiver.mid,capture:track}],error:_notification.default.exception}):tracks.push({type:"audio",capture:track,recv:!1})})),!tracks.length)return videoStream;this.videoroom.createOffer({tracks:tracks,success:jsep=>{this.videoroom.send({message:{request:"configure",video:!0,audio:!0},jsep:jsep})},error:function(error){_notification.default.alert("WebRTC error... ",error.message)}})}return videoStream})).catch(_notification.default.exception)}}class VideoTime extends _videotime.default{initialize(contextid,token,peerid){return _log.default.debug("Initializing Video Time "+this.elementId),this.contextid=contextid,this.peerid=peerid,_ajax.default.call([{methodname:"videotimeplugin_live_get_room",args:{contextid:contextid},done:response=>{const socket=new _socket.default(contextid,token);this.iceservers=JSON.parse(response.iceservers),this.roomid=response.roomid,this.server=response.server,rooms[String(contextid)]={contextid:contextid,peerid:peerid,roomid:response.roomid,server:response.server,iceServers:JSON.parse(response.iceservers)},this.roomid=response.roomid,document.querySelector('[data-contextid="'+this.contextid+'"] .videotime-control').classList.remove("hidden"),socket.subscribe((()=>{_ajax.default.call([{methodname:"videotimeplugin_live_get_feed",args:{contextid:contextid},done:response=>{const room=rooms[String(contextid)];room.publish&&room.publish.restart&&(response.feed==peerid&&this.unpublish(),room.publish=null),this.subscribeTo(Number(response.feed))},fail:_notification.default.exception}])}))},fail:_notification.default.exception}]),this.addListeners(),!0}addListeners(){document.querySelector("body").removeEventListener("click",handleClick),document.querySelector("body").addEventListener("click",handleClick)}subscribeTo(source){_log.default.debug(source);const room=rooms[String(this.contextid)];if(document.querySelectorAll('[data-contextid="'+this.contextid+'"][data-action="publish"]').forEach((button=>{Number(this.peerid),button.classList.remove("hidden")})),document.querySelectorAll('[data-contextid="'+this.contextid+'"][data-action="unpublish"]').forEach((button=>{Number(this.peerid),button.classList.remove("hidden")})),_log.default.debug(source),!this.remoteFeed||this.remoteFeed.creatingSubscription||this.remoteFeed.restart)this.remoteFeed&&this.remoteFeed.restart?this.remoteFeed.current!=source&&(this.remoteFeed=null,this.subscribeTo(source)):this.remoteFeed?setTimeout((()=>{this.subscribeTo(source)}),500):source&&(this.remoteFeed=new Subscribe(this.contextid,this.iceservers,this.roomid,this.server,this.peerid,source),this.remoteFeed.remoteVideo=document.getElementById(this.elementId),this.remoteFeed.remoteAudio=document.getElementById(this.elementId).parentNode.querySelector("audio"),this.remoteFeed.muteAudio=room.publish&&room.publish.feed===source,this.remoteFeed.startConnection(source),document.querySelectorAll('[data-contextid="'+this.contextid+'"] img.poster-img').forEach((img=>{img.classList.add("hidden")})),document.querySelectorAll('[data-contextid="'+this.contextid+'"] video').forEach((img=>{img.classList.remove("hidden")})));else{const update={request:"update",subscribe:[{feed:Number(source)}],unsubscribe:[{feed:Number(this.remoteFeed.current)}]};!source&&this.remoteFeed.current?delete update.subscribe:source&&!this.remoteFeed.current&&delete update.unsubscribe,this.remoteFeed.current!=source&&(this.remoteFeed.muteAudio=room.publish&&room.publish.feed===source,this.remoteFeed.videoroom.send({message:update}),this.remoteFeed.audioTrack&&(this.remoteFeed.audioTrack.enabled=!this.remoteFeed.muteAudio),room.publish&&this.remoteFeed.current==room.publish.feed&&(room.publish.handleClose(),room.publish=null),this.remoteFeed.current=source,!source&&this.remoteFeed&&(this.remoteFeed.handleClose(),this.remoteFeed=null),_log.default.debug('[data-contextid="'+this.contextid+'"] img.poster-img'),Number(source)?(document.querySelectorAll('[data-contextid="'+this.contextid+'"] img.poster-img').forEach((img=>{img.classList.add("hidden")})),document.querySelectorAll('[data-contextid="'+this.contextid+'"] video').forEach((video=>{video.classList.remove("hidden")}))):(document.querySelectorAll('[data-contextid="'+this.contextid+'"] img.poster-img').forEach((img=>{img.classList.remove("hidden")})),document.querySelectorAll('[data-contextid="'+this.contextid+'"] video').forEach((video=>{video.classList.add("hidden")}))))}}}_exports.default=VideoTime;const handleClick=function(e){const button=e.target.closest('[data-roomid] [data-action="publish"], [data-roomid] [data-action="unpublish"],[data-roomid] [data-action="mute"], [data-roomid] [data-action="unmute"]');if(button){const action=button.getAttribute("data-action"),contextid=e.target.closest("[data-contextid]").getAttribute("data-contextid"),room=rooms[String(contextid)],iceServers=room.iceServers,peerid=room.peerid,roomid=room.roomid,server=room.server,type=button.getAttribute("data-type");e.stopPropagation(),e.preventDefault(),"publish"!=action||room.publish&&!room.publish.restart?("mute"!=action&&"unmute"!=action||(button.classList.add("hidden"),button.parentNode.querySelectorAll('[data-action="mute"], [data-action="unmute"]').forEach((button=>{button.getAttribute("data-action")!=action&&button.classList.remove("hidden")}))),room.publish&&room.publish.handleClick(e)):(room.publish=new Publish(contextid,iceServers,roomid,server,peerid),"display"==type?room.publish.shareDisplay():room.publish.shareCamera(),room.publish.startConnection())}};class Subscribe extends _subscribe.default{register(pluginHandle){return _ajax.default.call([{args:{handle:pluginHandle.getId(),id:Number(this.contextid),plugin:pluginHandle.plugin,room:this.roomid,ptype:!1,feed:this.feed,session:pluginHandle.session.getSessionId()},contextid:this.contextid,fail:_notification.default.exception,methodname:"videotimeplugin_live_join_room"}])[0]}attachAudio(audioStream){_janusGateway.default.attachMediaStream(this.remoteVideo.parentNode.querySelector("audio"),audioStream),audioStream.getTracks().forEach((track=>{this.audioTrack=track,track.enabled=!this.muteAudio}))}attachVideo(videoStream){_janusGateway.default.attachMediaStream(this.remoteVideo,videoStream)}}return _exports.default})); //# sourceMappingURL=videotime.min.js.map \ No newline at end of file diff --git a/plugin/live/amd/build/videotime.min.js.map b/plugin/live/amd/build/videotime.min.js.map index 13b53157..bf8b930a 100644 --- a/plugin/live/amd/build/videotime.min.js.map +++ b/plugin/live/amd/build/videotime.min.js.map @@ -1 +1 @@ -{"version":3,"file":"videotime.min.js","sources":["../src/videotime.js"],"sourcesContent":["/*\n * Video time player specific js\n *\n * @package videotimeplugin_live\n * @module videotimeplugin_live/videotime\n * @copyright 2022 bdecent gmbh \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from \"core/ajax\";\nimport VideoTimeBase from \"mod_videotime/videotime\";\nimport Janus from 'block_deft/janus-gateway';\nimport Log from \"core/log\";\nimport Notification from \"core/notification\";\nimport PublishBase from \"block_deft/publish\";\nimport SubscribeBase from \"block_deft/subscribe\";\nimport Socket from \"videotimeplugin_live/socket\";\n\nvar rooms = {};\n\nclass Publish extends PublishBase {\n /**\n * Register the room\n *\n * @param {object} pluginHandle\n * @return {Promise}\n */\n register(pluginHandle) {\n // Try a registration\n return Ajax.call([{\n args: {\n handle: pluginHandle.getId(),\n id: Number(this.contextid),\n plugin: pluginHandle.plugin,\n room: this.roomid,\n ptype: this.ptype == 'publish',\n session: pluginHandle.session.getSessionId()\n },\n contextid: this.contextid,\n fail: Notification.exception,\n methodname: 'videotimeplugin_live_join_room'\n }])[0].then(response => {\n this.feed = response.id;\n }).catch(Notification.exception);\n }\n\n /**\n * Publish current video feed\n */\n publishFeed() {\n return Ajax.call([{\n args: {\n id: Number(this.feed),\n room: this.roomid,\n },\n contextid: this.contextid,\n fail: Notification.exception,\n methodname: 'videotimeplugin_live_publish_feed'\n }])[0];\n }\n\n\n /**\n * Stop video feed\n */\n unpublish() {\n return Ajax.call([{\n args: {\n id: Number(this.feed),\n publish: false,\n room: this.roomid\n },\n contextid: this.contextid,\n fail: Notification.exception,\n methodname: 'videotimeplugin_live_publish_feed'\n }])[0];\n }\n\n handleClose() {\n document.querySelectorAll(\n '[data-contextid=\"' + this.contextid + '\"][data-action=\"publish\"]'\n ).forEach(button => {\n button.classList.remove('hidden');\n });\n\n this.janus.destroy();\n\n [\n this.currentCamera || Promise.resolve(null),\n this.currentDisplay || Promise.resolve(null),\n ].forEach(videoInput => {\n videoInput.then(videoStream => {\n if (videoStream) {\n videoStream.getVideoTracks().forEach(track => {\n track.enabled = false;\n track.stop();\n });\n }\n\n return null;\n }).catch(Notification.exception);\n });\n }\n\n onLocalTrack(track, on) {\n const remoteStream = new MediaStream([track]);\n if (!on) {\n return;\n }\n remoteStream.mid = track.mid;\n Log.debug(on);\n Log.debug(remoteStream);\n Janus.attachMediaStream(\n document.getElementById('video-controls-' + this.tracks[track.id]),\n remoteStream\n );\n }\n\n handleClick(e) {\n const button = e.target.closest(\n '[data-contextid=\"' + this.contextid + '\"][data-action=\"publish\"], [data-contextid=\"'\n + this.contextid + '\"][data-action=\"unpublish\"]'\n );\n if (button) {\n const action = button.getAttribute('data-action'),\n type = button.getAttribute('data-type') || 'camera';\n e.stopPropagation();\n e.preventDefault();\n document.querySelectorAll(\n '[data-region=\"deft-venue\"] [data-action=\"publish\"], [data-region=\"deft-venue\"] [data-action=\"unpublish\"]'\n ).forEach(button => {\n if ((button.getAttribute('data-action') != action) || (button.getAttribute('data-type') != type)) {\n button.classList.remove('hidden');\n }\n });\n switch (action) {\n case 'publish':\n Log.debug(type);\n if (type == 'display') {\n this.shareDisplay();\n } else {\n this.shareCamera();\n }\n\n this.videoInput.then(videoStream => {\n const tracks = [];\n this.tracks = this.tracks || {};\n if (videoStream) {\n const transceiver = this.getTransceiver();\n videoStream.getVideoTracks().forEach(track => {\n track.addEventListener('ended', () => {\n if (this.selectedTrack != track.id) {\n this.unpublish();\n }\n });\n if (transceiver) {\n this.videoroom.replaceTracks({\n tracks: [{\n type: 'video',\n mid: transceiver.mid,\n capture: track\n }],\n error: Notification.exception\n });\n\n this.selectedTrack = track;\n return;\n }\n tracks.push({\n type: 'video',\n capture: track,\n recv: false\n });\n this.selectedTrack = track;\n });\n if (!tracks.length) {\n return videoStream;\n }\n videoStream.getAudioTracks().forEach(track => {\n tracks.push({\n type: 'audio',\n capture: track,\n recv: false\n });\n });\n if (!tracks.length) {\n return videoStream;\n }\n this.videoroom.createOffer({\n tracks: tracks,\n success: (jsep) => {\n const publish = {\n request: \"configure\",\n video: true,\n audio: true\n };\n this.videoroom.send({\n message: publish,\n jsep: jsep\n });\n },\n error: function(error) {\n Notification.alert(\"WebRTC error... \", error.message);\n }\n });\n }\n\n return videoStream;\n }).catch(Notification.exception);\n break;\n case 'unpublish':\n this.unpublish();\n }\n }\n\n return true;\n }\n\n /**\n * Set video source to user camera\n */\n shareCamera() {\n const videoInput = this.videoInput,\n currentCamera = this.currentCamera || Promise.resolve(null);\n\n this.videoInput = currentCamera.then(videoStream => {\n if (videoStream) {\n return videoStream;\n } else {\n const cameraInput = navigator.mediaDevices.getUserMedia({\n video: true,\n audio: false\n });\n\n this.currentCamera = cameraInput.catch(() => {\n return currentCamera;\n });\n\n return cameraInput.then(videoStream => {\n this.tracks = this.tracks || {};\n videoStream.getTracks().forEach(track => {\n this.tracks[track.id] = 'camera';\n });\n\n return videoStream;\n }).catch((e) => {\n Log.debug(e);\n\n return videoInput;\n });\n }\n });\n }\n\n /**\n * Set video source to display surface\n */\n shareDisplay() {\n const videoInput = this.videoInput,\n currentDisplay = this.currentDisplay || Promise.resolve(null),\n displayInput = navigator.mediaDevices.getDisplayMedia({\n video: true,\n audio: false\n });\n\n this.videoInput = displayInput.then(videoStream => {\n if (videoInput) {\n videoInput.then(videoStream => {\n if (videoStream) {\n videoStream.getTracks().forEach(track => {\n Log.debug(track); //track.stop();\n });\n }\n return videoStream;\n }).catch(Notification.exception);\n }\n this.tracks = this.tracks || {};\n videoStream.getTracks().forEach(track => {\n this.tracks[track.id] = 'display';\n });\n\n return videoStream;\n }).catch((e) => {\n Log.debug(e);\n\n return videoInput;\n });\n\n this.currentDisplay = displayInput.then(videoStream => {\n currentDisplay.then(videoStream => {\n if (videoStream) {\n videoStream.getTracks().forEach(track => {\n Log.debug('stop track');\n Log.debug(track);\n track.stop();\n });\n }\n });\n\n return videoStream;\n }).catch((e) => {\n Log.debug(e);\n\n return currentDisplay;\n });\n }\n}\n\nexport default class VideoTime extends VideoTimeBase {\n initialize(contextid, token, peerid) {\n Log.debug(\"Initializing Video Time \" + this.elementId);\n\n this.contextid = contextid;\n this.peerid = peerid;\n\n Ajax.call([{\n methodname: 'videotimeplugin_live_get_room',\n args: {contextid: contextid},\n done: (response) => {\n const socket = new Socket(contextid, token);\n\n this.iceservers = JSON.parse(response.iceservers);\n this.roomid = response.roomid;\n this.server = response.server;\n\n rooms[String(contextid)] = {\n contextid: contextid,\n peerid: peerid,\n roomid: response.roomid,\n server: response.server,\n iceServers: JSON.parse(response.iceservers)\n };\n this.roomid = response.roomid;\n\n socket.subscribe(() => {\n Ajax.call([{\n methodname: 'videotimeplugin_live_get_feed',\n args: {contextid: contextid},\n done: (response) => {\n const room = rooms[String(contextid)];\n if (room.publish && room.publish.restart) {\n if (response.feed == peerid) {\n this.unpublish();\n }\n room.publish = null;\n }\n this.subscribeTo(Number(response.feed));\n },\n fail: Notification.exception\n }]);\n });\n },\n fail: Notification.exception\n }]);\n\n this.addListeners();\n\n return true;\n }\n\n /**\n * Register player events to respond to user interaction and play progress.\n */\n addListeners() {\n document.querySelector('body').removeEventListener('click', handleClick);\n document.querySelector('body').addEventListener('click', handleClick);\n return;\n }\n\n /**\n * Subscribe to feed\n *\n * @param {int} source Feed to subscribe\n */\n subscribeTo(source) {\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"][data-action=\"publish\"]').forEach(button => {\n if (source == Number(this.peerid)) {\n button.classList.remove('hidden');\n } else {\n button.classList.remove('hidden');\n }\n });\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"][data-action=\"unpublish\"]').forEach(button => {\n if (source == Number(this.peerid)) {\n button.classList.remove('hidden');\n } else {\n button.classList.remove('hidden');\n }\n });\n Log.debug(source);\n\n if (this.remoteFeed && !this.remoteFeed.creatingSubscription && !this.remoteFeed.restart) {\n const update = {\n request: 'update',\n subscribe: [{\n feed: Number(source)\n }],\n unsubscribe: [{\n feed: Number(this.remoteFeed.current)\n }]\n };\n\n if (!source && this.remoteFeed.current) {\n delete update.subscribe;\n } else if (source && !this.remoteFeed.current) {\n delete update.unsubscribe;\n }\n\n if (this.remoteFeed.current != source) {\n const room = rooms[String(this.contextid)];\n this.remoteFeed.videoroom.send({message: update});\n if (room.publish && this.remoteFeed.current == room.publish.feed) {\n room.publish.handleClose();\n room.publish = null;\n }\n this.remoteFeed.current = source;\n if (!source && this.remoteFeed.current) {\n this.remoteFeed.handleClose();\n this.remoteFeed = null;\n }\n Log.debug('[data-contextid=\"' + this.contextid + '\"] img.poster-img');\n if (Number(source)) {\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] img.poster-img').forEach(img => {\n img.classList.add('hidden');\n });\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] video').forEach(video => {\n video.classList.remove('hidden');\n });\n } else {\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] img.poster-img').forEach(img => {\n img.classList.remove('hidden');\n });\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] video').forEach(video => {\n video.classList.add('hidden');\n });\n }\n }\n } else if (this.remoteFeed && this.remoteFeed.restart) {\n if (this.remoteFeed.current != source) {\n this.remoteFeed = null;\n this.subscribeTo(source);\n }\n } else if (this.remoteFeed) {\n setTimeout(() => {\n this.subscribeTo(source);\n }, 500);\n } else if (source) {\n this.remoteFeed = new Subscribe(this.contextid, this.iceservers, this.roomid, this.server, this.peerid, source);\n this.remoteFeed.remoteVideo = document.getElementById(this.elementId);\n this.remoteFeed.remoteAudio = document.getElementById(this.elementId).parentNode.querySelector('audio');\n this.remoteFeed.startConnection(source);\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] img.poster-img').forEach(img => {\n img.classList.add('hidden');\n });\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] video').forEach(img => {\n img.classList.remove('hidden');\n });\n }\n }\n}\n\nconst handleClick = function(e) {\n const button = e.target.closest('[data-roomid] [data-action=\"publish\"], [data-roomid] [data-action=\"unpublish\"]');\n if (button) {\n const action = button.getAttribute('data-action'),\n contextid = e.target.closest('[data-contextid]').getAttribute('data-contextid'),\n room = rooms[String(contextid)],\n iceServers = room.iceServers,\n peerid = room.peerid,\n roomid = room.roomid,\n server = room.server,\n type = button.getAttribute('data-type');\n e.stopPropagation();\n e.preventDefault();\n if ((action == 'publish') && (!room.publish || room.publish.restart)) {\n room.publish = new Publish(contextid, iceServers, roomid, server, peerid);\n if (type == 'display') {\n room.publish.shareDisplay();\n } else {\n room.publish.shareCamera();\n }\n room.publish.startConnection();\n } else {\n room.publish.handleClick(e);\n }\n }\n};\n\nclass Subscribe extends SubscribeBase {\n /**\n * Register the room\n *\n * @param {object} pluginHandle\n * @return {Promise}\n */\n register(pluginHandle) {\n // Try a registration\n return Ajax.call([{\n args: {\n handle: pluginHandle.getId(),\n id: Number(this.contextid),\n plugin: pluginHandle.plugin,\n room: this.roomid,\n ptype: false,\n feed: this.feed,\n session: pluginHandle.session.getSessionId()\n },\n contextid: this.contextid,\n fail: Notification.exception,\n methodname: 'videotimeplugin_live_join_room'\n }])[0];\n }\n}\n"],"names":["rooms","Publish","PublishBase","register","pluginHandle","Ajax","call","args","handle","getId","id","Number","this","contextid","plugin","room","roomid","ptype","session","getSessionId","fail","Notification","exception","methodname","then","response","feed","catch","publishFeed","unpublish","publish","handleClose","document","querySelectorAll","forEach","button","classList","remove","janus","destroy","currentCamera","Promise","resolve","currentDisplay","videoInput","videoStream","getVideoTracks","track","enabled","stop","onLocalTrack","on","remoteStream","MediaStream","mid","debug","attachMediaStream","getElementById","tracks","handleClick","e","target","closest","action","getAttribute","type","stopPropagation","preventDefault","shareDisplay","shareCamera","transceiver","getTransceiver","addEventListener","selectedTrack","videoroom","replaceTracks","capture","error","push","recv","length","getAudioTracks","createOffer","success","jsep","send","message","request","video","audio","alert","cameraInput","navigator","mediaDevices","getUserMedia","getTracks","displayInput","getDisplayMedia","VideoTime","VideoTimeBase","initialize","token","peerid","elementId","done","socket","Socket","iceservers","JSON","parse","server","String","iceServers","subscribe","restart","subscribeTo","addListeners","querySelector","removeEventListener","source","remoteFeed","creatingSubscription","current","setTimeout","Subscribe","remoteVideo","remoteAudio","parentNode","startConnection","img","add","update","unsubscribe","SubscribeBase"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"videotime.min.js","sources":["../src/videotime.js"],"sourcesContent":["/*\n * Video time player specific js\n *\n * @package videotimeplugin_live\n * @module videotimeplugin_live/videotime\n * @copyright 2022 bdecent gmbh \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from \"core/ajax\";\nimport VideoTimeBase from \"mod_videotime/videotime\";\nimport Janus from 'block_deft/janus-gateway';\nimport Log from \"core/log\";\nimport Notification from \"core/notification\";\nimport PublishBase from \"block_deft/publish\";\nimport SubscribeBase from \"block_deft/subscribe\";\nimport Socket from \"videotimeplugin_live/socket\";\n\nvar rooms = {};\n\nclass Publish extends PublishBase {\n /**\n * Register the room\n *\n * @param {object} pluginHandle\n * @return {Promise}\n */\n register(pluginHandle) {\n // Try a registration\n return Ajax.call([{\n args: {\n handle: pluginHandle.getId(),\n id: Number(this.contextid),\n plugin: pluginHandle.plugin,\n room: this.roomid,\n ptype: this.ptype == 'publish',\n session: pluginHandle.session.getSessionId()\n },\n contextid: this.contextid,\n fail: Notification.exception,\n methodname: 'videotimeplugin_live_join_room'\n }])[0].then(response => {\n this.feed = response.id;\n }).catch(Notification.exception);\n }\n\n /**\n * Publish current video feed\n */\n publishFeed() {\n return Ajax.call([{\n args: {\n id: Number(this.feed),\n room: this.roomid,\n },\n contextid: this.contextid,\n fail: Notification.exception,\n methodname: 'videotimeplugin_live_publish_feed'\n }])[0];\n }\n\n\n /**\n * Stop video feed\n */\n unpublish() {\n return Ajax.call([{\n args: {\n id: Number(this.feed),\n publish: false,\n room: this.roomid\n },\n contextid: this.contextid,\n fail: Notification.exception,\n methodname: 'videotimeplugin_live_publish_feed'\n }])[0];\n }\n\n handleClose() {\n document.querySelectorAll(\n '[data-contextid=\"' + this.contextid + '\"][data-action=\"publish\"]'\n ).forEach(button => {\n button.classList.remove('hidden');\n });\n\n this.janus.destroy();\n\n [\n this.currentCamera || Promise.resolve(null),\n this.currentDisplay || Promise.resolve(null),\n ].forEach(videoInput => {\n videoInput.then(videoStream => {\n if (videoStream) {\n videoStream.getVideoTracks().forEach(track => {\n track.enabled = false;\n track.stop();\n });\n }\n\n return null;\n }).catch(Notification.exception);\n });\n }\n\n onLocalTrack(track, on) {\n const remoteStream = new MediaStream([track]);\n if (!on || (track.kind == 'audio')) {\n return;\n }\n remoteStream.mid = track.mid;\n Log.debug(on);\n Log.debug(remoteStream);\n Janus.attachMediaStream(\n document.getElementById('video-controls-' + this.tracks[track.id]),\n remoteStream\n );\n }\n\n handleClick(e) {\n const button = e.target.closest(\n '[data-contextid=\"' + this.contextid + '\"][data-action=\"publish\"], [data-contextid=\"'\n + this.contextid + '\"][data-action=\"mute\"], [data-contextid=\"'\n + this.contextid + '\"][data-action=\"unmute\"], [data-contextid=\"'\n + this.contextid + '\"][data-action=\"unpublish\"]'\n );\n if (button) {\n const action = button.getAttribute('data-action'),\n type = button.getAttribute('data-type') || 'camera',\n transceiver = this.getTransceiver('audio');\n e.stopPropagation();\n e.preventDefault();\n document.querySelectorAll(\n '[data-region=\"deft-venue\"] [data-action=\"publish\"], [data-region=\"deft-venue\"] [data-action=\"unpublish\"]'\n ).forEach(button => {\n if ((button.getAttribute('data-action') != action) || (button.getAttribute('data-type') != type)) {\n button.classList.remove('hidden');\n }\n });\n switch (action) {\n case 'mute':\n if (transceiver) {\n transceiver.sender.track.enabled = false;\n }\n break;\n case 'unmute':\n if (transceiver) {\n transceiver.sender.track.enabled = true;\n }\n break;\n case 'publish':\n Log.debug(type);\n if (type == 'display') {\n this.shareDisplay();\n } else {\n this.shareCamera();\n }\n\n this.processStream([]);\n break;\n case 'unpublish':\n this.unpublish();\n }\n }\n\n return true;\n }\n\n /**\n * Set video source to user camera\n */\n shareCamera() {\n const videoInput = this.videoInput,\n currentCamera = this.currentCamera || Promise.resolve(null);\n\n this.videoInput = currentCamera.then(videoStream => {\n if (videoStream) {\n return videoStream;\n } else {\n const cameraInput = navigator.mediaDevices.getUserMedia({\n video: true,\n audio: true\n });\n\n this.currentCamera = cameraInput.catch(() => {\n return currentCamera;\n });\n\n return cameraInput.then(videoStream => {\n this.tracks = this.tracks || {};\n videoStream.getTracks().forEach(track => {\n this.tracks[track.id] = 'camera';\n });\n\n return videoStream;\n }).catch((e) => {\n Log.debug(e);\n\n return videoInput;\n });\n }\n });\n }\n\n /**\n * Set video source to display surface\n */\n shareDisplay() {\n const videoInput = this.videoInput,\n currentDisplay = this.currentDisplay || Promise.resolve(null),\n displayInput = navigator.mediaDevices.getDisplayMedia({\n video: true,\n audio: true\n });\n\n this.videoInput = displayInput.then(videoStream => {\n if (videoInput) {\n videoInput.then(videoStream => {\n if (videoStream) {\n videoStream.getTracks().forEach(track => {\n Log.debug(track); //track.stop();\n });\n }\n return videoStream;\n }).catch(Notification.exception);\n }\n this.tracks = this.tracks || {};\n videoStream.getTracks().forEach(track => {\n this.tracks[track.id] = 'display';\n });\n\n return videoStream;\n }).catch((e) => {\n Log.debug(e);\n\n return videoInput;\n });\n\n this.currentDisplay = displayInput.then(videoStream => {\n currentDisplay.then(videoStream => {\n if (videoStream) {\n videoStream.getTracks().forEach(track => {\n Log.debug('stop track');\n Log.debug(track);\n track.stop();\n });\n }\n });\n\n return videoStream;\n }).catch((e) => {\n Log.debug(e);\n\n return currentDisplay;\n });\n }\n\n processStream(tracks) {\n this.videoInput.then(videoStream => {\n this.tracks = this.tracks || {};\n if (videoStream) {\n const audiotransceiver = this.getTransceiver('audio'),\n videotransceiver = this.getTransceiver('video');\n videoStream.getVideoTracks().forEach(track => {\n track.addEventListener('ended', () => {\n if (this.selectedTrack != track.id) {\n this.unpublish();\n }\n });\n this.selectedTrack = track;\n if (videotransceiver) {\n this.videoroom.replaceTracks({\n tracks: [{\n type: 'video',\n mid: videotransceiver.mid,\n capture: track\n }],\n error: Notification.exception\n });\n\n return;\n }\n tracks.push({\n type: 'video',\n capture: track,\n recv: false\n });\n });\n videoStream.getAudioTracks().forEach(track => {\n track.addEventListener('ended', () => {\n if (this.selectedTrack != track.id) {\n this.unpublish();\n }\n });\n if (document.querySelector('.hidden[data-action=\"mute\"][data-contextid=\"' + this.contextid + '\"]')) {\n track.enabled = false;\n }\n\n if (audiotransceiver) {\n this.videoroom.replaceTracks({\n tracks: [{\n type: 'audio',\n mid: audiotransceiver.mid,\n capture: track\n }],\n error: Notification.exception\n });\n\n return;\n }\n tracks.push({\n type: 'audio',\n capture: track,\n recv: false\n });\n });\n if (!tracks.length) {\n return videoStream;\n }\n this.videoroom.createOffer({\n tracks: tracks,\n success: (jsep) => {\n const publish = {\n request: \"configure\",\n video: true,\n audio: true\n };\n this.videoroom.send({\n message: publish,\n jsep: jsep\n });\n },\n error: function(error) {\n Notification.alert(\"WebRTC error... \", error.message);\n }\n });\n }\n\n return videoStream;\n }).catch(Notification.exception);\n }\n}\n\nexport default class VideoTime extends VideoTimeBase {\n initialize(contextid, token, peerid) {\n Log.debug(\"Initializing Video Time \" + this.elementId);\n\n this.contextid = contextid;\n this.peerid = peerid;\n\n Ajax.call([{\n methodname: 'videotimeplugin_live_get_room',\n args: {contextid: contextid},\n done: (response) => {\n const socket = new Socket(contextid, token);\n\n this.iceservers = JSON.parse(response.iceservers);\n this.roomid = response.roomid;\n this.server = response.server;\n\n rooms[String(contextid)] = {\n contextid: contextid,\n peerid: peerid,\n roomid: response.roomid,\n server: response.server,\n iceServers: JSON.parse(response.iceservers)\n };\n this.roomid = response.roomid;\n\n document.querySelector('[data-contextid=\"' + this.contextid + '\"] .videotime-control').classList.remove('hidden');\n\n socket.subscribe(() => {\n Ajax.call([{\n methodname: 'videotimeplugin_live_get_feed',\n args: {contextid: contextid},\n done: (response) => {\n const room = rooms[String(contextid)];\n if (room.publish && room.publish.restart) {\n if (response.feed == peerid) {\n this.unpublish();\n }\n room.publish = null;\n }\n this.subscribeTo(Number(response.feed));\n },\n fail: Notification.exception\n }]);\n });\n },\n fail: Notification.exception\n }]);\n\n this.addListeners();\n\n return true;\n }\n\n /**\n * Register player events to respond to user interaction and play progress.\n */\n addListeners() {\n document.querySelector('body').removeEventListener('click', handleClick);\n document.querySelector('body').addEventListener('click', handleClick);\n return;\n }\n\n /**\n * Subscribe to feed\n *\n * @param {int} source Feed to subscribe\n */\n subscribeTo(source) {\n Log.debug(source);\n const room = rooms[String(this.contextid)];\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"][data-action=\"publish\"]').forEach(button => {\n if (source == Number(this.peerid)) {\n button.classList.remove('hidden');\n } else {\n button.classList.remove('hidden');\n }\n });\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"][data-action=\"unpublish\"]').forEach(button => {\n if (source == Number(this.peerid)) {\n button.classList.remove('hidden');\n } else {\n button.classList.remove('hidden');\n }\n });\n Log.debug(source);\n\n if (this.remoteFeed && !this.remoteFeed.creatingSubscription && !this.remoteFeed.restart) {\n const update = {\n request: 'update',\n subscribe: [{\n feed: Number(source)\n }],\n unsubscribe: [{\n feed: Number(this.remoteFeed.current)\n }]\n };\n\n if (!source && this.remoteFeed.current) {\n delete update.subscribe;\n } else if (source && !this.remoteFeed.current) {\n delete update.unsubscribe;\n }\n\n if (this.remoteFeed.current != source) {\n this.remoteFeed.muteAudio = room.publish && (room.publish.feed === source);\n this.remoteFeed.videoroom.send({message: update});\n if (this.remoteFeed.audioTrack) {\n this.remoteFeed.audioTrack.enabled = !this.remoteFeed.muteAudio;\n }\n\n if (room.publish && this.remoteFeed.current == room.publish.feed) {\n room.publish.handleClose();\n room.publish = null;\n }\n this.remoteFeed.current = source;\n if (!source && this.remoteFeed) {\n this.remoteFeed.handleClose();\n this.remoteFeed = null;\n }\n Log.debug('[data-contextid=\"' + this.contextid + '\"] img.poster-img');\n if (Number(source)) {\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] img.poster-img').forEach(img => {\n img.classList.add('hidden');\n });\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] video').forEach(video => {\n video.classList.remove('hidden');\n });\n } else {\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] img.poster-img').forEach(img => {\n img.classList.remove('hidden');\n });\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] video').forEach(video => {\n video.classList.add('hidden');\n });\n }\n }\n } else if (this.remoteFeed && this.remoteFeed.restart) {\n if (this.remoteFeed.current != source) {\n this.remoteFeed = null;\n this.subscribeTo(source);\n }\n } else if (this.remoteFeed) {\n setTimeout(() => {\n this.subscribeTo(source);\n }, 500);\n } else if (source) {\n this.remoteFeed = new Subscribe(this.contextid, this.iceservers, this.roomid, this.server, this.peerid, source);\n this.remoteFeed.remoteVideo = document.getElementById(this.elementId);\n this.remoteFeed.remoteAudio = document.getElementById(this.elementId).parentNode.querySelector('audio');\n this.remoteFeed.muteAudio = room.publish && (room.publish.feed === source);\n this.remoteFeed.startConnection(source);\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] img.poster-img').forEach(img => {\n img.classList.add('hidden');\n });\n document.querySelectorAll('[data-contextid=\"' + this.contextid + '\"] video').forEach(img => {\n img.classList.remove('hidden');\n });\n }\n }\n}\n\nconst handleClick = function(e) {\n const button = e.target.closest(\n '[data-roomid] [data-action=\"publish\"], [data-roomid] [data-action=\"unpublish\"],'\n + '[data-roomid] [data-action=\"mute\"], [data-roomid] [data-action=\"unmute\"]'\n );\n if (button) {\n const action = button.getAttribute('data-action'),\n contextid = e.target.closest('[data-contextid]').getAttribute('data-contextid'),\n room = rooms[String(contextid)],\n iceServers = room.iceServers,\n peerid = room.peerid,\n roomid = room.roomid,\n server = room.server,\n type = button.getAttribute('data-type');\n e.stopPropagation();\n e.preventDefault();\n if ((action == 'publish') && (!room.publish || room.publish.restart)) {\n room.publish = new Publish(contextid, iceServers, roomid, server, peerid);\n if (type == 'display') {\n room.publish.shareDisplay();\n } else {\n room.publish.shareCamera();\n }\n room.publish.startConnection();\n } else {\n if ((action == 'mute') || (action == 'unmute')) {\n button.classList.add('hidden');\n button.parentNode.querySelectorAll('[data-action=\"mute\"], [data-action=\"unmute\"]').forEach(button => {\n if (button.getAttribute('data-action') != action) {\n button.classList.remove('hidden');\n }\n });\n }\n if (room.publish) {\n room.publish.handleClick(e);\n }\n }\n }\n};\n\nclass Subscribe extends SubscribeBase {\n /**\n * Register the room\n *\n * @param {object} pluginHandle\n * @return {Promise}\n */\n register(pluginHandle) {\n // Try a registration\n return Ajax.call([{\n args: {\n handle: pluginHandle.getId(),\n id: Number(this.contextid),\n plugin: pluginHandle.plugin,\n room: this.roomid,\n ptype: false,\n feed: this.feed,\n session: pluginHandle.session.getSessionId()\n },\n contextid: this.contextid,\n fail: Notification.exception,\n methodname: 'videotimeplugin_live_join_room'\n }])[0];\n }\n\n attachAudio(audioStream) {\n Janus.attachMediaStream(\n this.remoteVideo.parentNode.querySelector('audio'),\n audioStream\n );\n audioStream.getTracks().forEach(track => {\n this.audioTrack = track;\n track.enabled = !this.muteAudio;\n });\n }\n\n attachVideo(videoStream) {\n Janus.attachMediaStream(\n this.remoteVideo,\n videoStream\n );\n }\n}\n"],"names":["rooms","Publish","PublishBase","register","pluginHandle","Ajax","call","args","handle","getId","id","Number","this","contextid","plugin","room","roomid","ptype","session","getSessionId","fail","Notification","exception","methodname","then","response","feed","catch","publishFeed","unpublish","publish","handleClose","document","querySelectorAll","forEach","button","classList","remove","janus","destroy","currentCamera","Promise","resolve","currentDisplay","videoInput","videoStream","getVideoTracks","track","enabled","stop","onLocalTrack","on","remoteStream","MediaStream","kind","mid","debug","attachMediaStream","getElementById","tracks","handleClick","e","target","closest","action","getAttribute","type","transceiver","getTransceiver","stopPropagation","preventDefault","sender","shareDisplay","shareCamera","processStream","cameraInput","navigator","mediaDevices","getUserMedia","video","audio","getTracks","displayInput","getDisplayMedia","audiotransceiver","videotransceiver","addEventListener","selectedTrack","videoroom","replaceTracks","capture","error","push","recv","getAudioTracks","querySelector","length","createOffer","success","jsep","send","message","request","alert","VideoTime","VideoTimeBase","initialize","token","peerid","elementId","done","socket","Socket","iceservers","JSON","parse","server","String","iceServers","subscribe","restart","subscribeTo","addListeners","removeEventListener","source","remoteFeed","creatingSubscription","current","setTimeout","Subscribe","remoteVideo","remoteAudio","parentNode","muteAudio","startConnection","img","add","update","unsubscribe","audioTrack","SubscribeBase","attachAudio","audioStream","attachVideo"],"mappings":"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"} \ No newline at end of file diff --git a/plugin/live/amd/src/videotime.js b/plugin/live/amd/src/videotime.js index dd8827a1..dfde1da9 100644 --- a/plugin/live/amd/src/videotime.js +++ b/plugin/live/amd/src/videotime.js @@ -104,7 +104,7 @@ class Publish extends PublishBase { onLocalTrack(track, on) { const remoteStream = new MediaStream([track]); - if (!on) { + if (!on || (track.kind == 'audio')) { return; } remoteStream.mid = track.mid; @@ -118,22 +118,35 @@ class Publish extends PublishBase { handleClick(e) { const button = e.target.closest( - '[data-contextid="' + this.contextid + '"][data-action="publish"], [data-contextid="' - + this.contextid + '"][data-action="unpublish"]' + '[data-contextid="' + this.contextid + '"][data-action="publish"], [data-contextid="' + + this.contextid + '"][data-action="mute"], [data-contextid="' + + this.contextid + '"][data-action="unmute"], [data-contextid="' + + this.contextid + '"][data-action="unpublish"]' ); if (button) { const action = button.getAttribute('data-action'), - type = button.getAttribute('data-type') || 'camera'; + type = button.getAttribute('data-type') || 'camera', + transceiver = this.getTransceiver('audio'); e.stopPropagation(); e.preventDefault(); document.querySelectorAll( - '[data-region="deft-venue"] [data-action="publish"], [data-region="deft-venue"] [data-action="unpublish"]' + '[data-region="deft-venue"] [data-action="publish"], [data-region="deft-venue"] [data-action="unpublish"]' ).forEach(button => { if ((button.getAttribute('data-action') != action) || (button.getAttribute('data-type') != type)) { button.classList.remove('hidden'); } }); switch (action) { + case 'mute': + if (transceiver) { + transceiver.sender.track.enabled = false; + } + break; + case 'unmute': + if (transceiver) { + transceiver.sender.track.enabled = true; + } + break; case 'publish': Log.debug(type); if (type == 'display') { @@ -142,71 +155,7 @@ class Publish extends PublishBase { this.shareCamera(); } - this.videoInput.then(videoStream => { - const tracks = []; - this.tracks = this.tracks || {}; - if (videoStream) { - const transceiver = this.getTransceiver(); - videoStream.getVideoTracks().forEach(track => { - track.addEventListener('ended', () => { - if (this.selectedTrack != track.id) { - this.unpublish(); - } - }); - if (transceiver) { - this.videoroom.replaceTracks({ - tracks: [{ - type: 'video', - mid: transceiver.mid, - capture: track - }], - error: Notification.exception - }); - - this.selectedTrack = track; - return; - } - tracks.push({ - type: 'video', - capture: track, - recv: false - }); - this.selectedTrack = track; - }); - if (!tracks.length) { - return videoStream; - } - videoStream.getAudioTracks().forEach(track => { - tracks.push({ - type: 'audio', - capture: track, - recv: false - }); - }); - if (!tracks.length) { - return videoStream; - } - this.videoroom.createOffer({ - tracks: tracks, - success: (jsep) => { - const publish = { - request: "configure", - video: true, - audio: true - }; - this.videoroom.send({ - message: publish, - jsep: jsep - }); - }, - error: function(error) { - Notification.alert("WebRTC error... ", error.message); - } - }); - } - - return videoStream; - }).catch(Notification.exception); + this.processStream([]); break; case 'unpublish': this.unpublish(); @@ -229,7 +178,7 @@ class Publish extends PublishBase { } else { const cameraInput = navigator.mediaDevices.getUserMedia({ video: true, - audio: false + audio: true }); this.currentCamera = cameraInput.catch(() => { @@ -260,7 +209,7 @@ class Publish extends PublishBase { currentDisplay = this.currentDisplay || Promise.resolve(null), displayInput = navigator.mediaDevices.getDisplayMedia({ video: true, - audio: false + audio: true }); this.videoInput = displayInput.then(videoStream => { @@ -304,6 +253,91 @@ class Publish extends PublishBase { return currentDisplay; }); } + + processStream(tracks) { + this.videoInput.then(videoStream => { + this.tracks = this.tracks || {}; + if (videoStream) { + const audiotransceiver = this.getTransceiver('audio'), + videotransceiver = this.getTransceiver('video'); + videoStream.getVideoTracks().forEach(track => { + track.addEventListener('ended', () => { + if (this.selectedTrack != track.id) { + this.unpublish(); + } + }); + this.selectedTrack = track; + if (videotransceiver) { + this.videoroom.replaceTracks({ + tracks: [{ + type: 'video', + mid: videotransceiver.mid, + capture: track + }], + error: Notification.exception + }); + + return; + } + tracks.push({ + type: 'video', + capture: track, + recv: false + }); + }); + videoStream.getAudioTracks().forEach(track => { + track.addEventListener('ended', () => { + if (this.selectedTrack != track.id) { + this.unpublish(); + } + }); + if (document.querySelector('.hidden[data-action="mute"][data-contextid="' + this.contextid + '"]')) { + track.enabled = false; + } + + if (audiotransceiver) { + this.videoroom.replaceTracks({ + tracks: [{ + type: 'audio', + mid: audiotransceiver.mid, + capture: track + }], + error: Notification.exception + }); + + return; + } + tracks.push({ + type: 'audio', + capture: track, + recv: false + }); + }); + if (!tracks.length) { + return videoStream; + } + this.videoroom.createOffer({ + tracks: tracks, + success: (jsep) => { + const publish = { + request: "configure", + video: true, + audio: true + }; + this.videoroom.send({ + message: publish, + jsep: jsep + }); + }, + error: function(error) { + Notification.alert("WebRTC error... ", error.message); + } + }); + } + + return videoStream; + }).catch(Notification.exception); + } } export default class VideoTime extends VideoTimeBase { @@ -332,6 +366,8 @@ export default class VideoTime extends VideoTimeBase { }; this.roomid = response.roomid; + document.querySelector('[data-contextid="' + this.contextid + '"] .videotime-control').classList.remove('hidden'); + socket.subscribe(() => { Ajax.call([{ methodname: 'videotimeplugin_live_get_feed', @@ -373,6 +409,8 @@ export default class VideoTime extends VideoTimeBase { * @param {int} source Feed to subscribe */ subscribeTo(source) { + Log.debug(source); + const room = rooms[String(this.contextid)]; document.querySelectorAll('[data-contextid="' + this.contextid + '"][data-action="publish"]').forEach(button => { if (source == Number(this.peerid)) { button.classList.remove('hidden'); @@ -407,14 +445,18 @@ export default class VideoTime extends VideoTimeBase { } if (this.remoteFeed.current != source) { - const room = rooms[String(this.contextid)]; + this.remoteFeed.muteAudio = room.publish && (room.publish.feed === source); this.remoteFeed.videoroom.send({message: update}); + if (this.remoteFeed.audioTrack) { + this.remoteFeed.audioTrack.enabled = !this.remoteFeed.muteAudio; + } + if (room.publish && this.remoteFeed.current == room.publish.feed) { room.publish.handleClose(); room.publish = null; } this.remoteFeed.current = source; - if (!source && this.remoteFeed.current) { + if (!source && this.remoteFeed) { this.remoteFeed.handleClose(); this.remoteFeed = null; } @@ -448,6 +490,7 @@ export default class VideoTime extends VideoTimeBase { this.remoteFeed = new Subscribe(this.contextid, this.iceservers, this.roomid, this.server, this.peerid, source); this.remoteFeed.remoteVideo = document.getElementById(this.elementId); this.remoteFeed.remoteAudio = document.getElementById(this.elementId).parentNode.querySelector('audio'); + this.remoteFeed.muteAudio = room.publish && (room.publish.feed === source); this.remoteFeed.startConnection(source); document.querySelectorAll('[data-contextid="' + this.contextid + '"] img.poster-img').forEach(img => { img.classList.add('hidden'); @@ -460,7 +503,10 @@ export default class VideoTime extends VideoTimeBase { } const handleClick = function(e) { - const button = e.target.closest('[data-roomid] [data-action="publish"], [data-roomid] [data-action="unpublish"]'); + const button = e.target.closest( + '[data-roomid] [data-action="publish"], [data-roomid] [data-action="unpublish"],' + + '[data-roomid] [data-action="mute"], [data-roomid] [data-action="unmute"]' + ); if (button) { const action = button.getAttribute('data-action'), contextid = e.target.closest('[data-contextid]').getAttribute('data-contextid'), @@ -481,7 +527,17 @@ const handleClick = function(e) { } room.publish.startConnection(); } else { - room.publish.handleClick(e); + if ((action == 'mute') || (action == 'unmute')) { + button.classList.add('hidden'); + button.parentNode.querySelectorAll('[data-action="mute"], [data-action="unmute"]').forEach(button => { + if (button.getAttribute('data-action') != action) { + button.classList.remove('hidden'); + } + }); + } + if (room.publish) { + room.publish.handleClick(e); + } } } }; @@ -510,4 +566,22 @@ class Subscribe extends SubscribeBase { methodname: 'videotimeplugin_live_join_room' }])[0]; } + + attachAudio(audioStream) { + Janus.attachMediaStream( + this.remoteVideo.parentNode.querySelector('audio'), + audioStream + ); + audioStream.getTracks().forEach(track => { + this.audioTrack = track; + track.enabled = !this.muteAudio; + }); + } + + attachVideo(videoStream) { + Janus.attachMediaStream( + this.remoteVideo, + videoStream + ); + } } diff --git a/plugin/live/classes/external/get_feed.php b/plugin/live/classes/external/get_feed.php index b68f959c..f0472f07 100644 --- a/plugin/live/classes/external/get_feed.php +++ b/plugin/live/classes/external/get_feed.php @@ -78,6 +78,18 @@ public static function execute($contextid): array { $data = json_decode($record->data) ?? new stdClass(); + if (empty($data->feed || !$DB->get_record( + 'videotimeplugin_live_peer', + [ + 'id' => $data->feed, + 'status' => 0, + ] + ))) { + return [ + 'feed' => 0, + ]; + } + return [ 'feed' => $data->feed ?? 0, ]; diff --git a/plugin/live/classes/external/publish_feed.php b/plugin/live/classes/external/publish_feed.php index b4a5525b..4f8fb94f 100644 --- a/plugin/live/classes/external/publish_feed.php +++ b/plugin/live/classes/external/publish_feed.php @@ -61,6 +61,19 @@ public static function execute($id, $publish, $room): array { ] ); + if (!$DB->get_record_select( + 'videotimeplugin_live_peer', + "id = :id AND status = 0 AND sessionid IN (SELECT id FROM {sessions} WHERE sid = :sid)", + [ + 'id' => $id, + 'sid' => session_id(), + ] + )) { + return [ + 'status' => false, + ]; + } + $cm = get_coursemodule_from_instance('videotime', $record->itemid); $context = context_module::instance($cm->id); self::validate_context($context); @@ -79,6 +92,9 @@ public static function execute($id, $publish, $room): array { $data = json_decode($record->data) ?? new stdClass(); if (!$publish && !empty($data->feed) && $data->feed == $id) { $data->feed = 0; + $DB->set_field('videotimeplugin_live_peer', 'status', 1, [ + 'id' => $id, + ]); } else if ($publish) { if ( !empty($data->feed) diff --git a/plugin/live/templates/controls.mustache b/plugin/live/templates/controls.mustache index 760fb154..8987ab33 100644 --- a/plugin/live/templates/controls.mustache +++ b/plugin/live/templates/controls.mustache @@ -59,17 +59,18 @@ > - diff --git a/plugin/live/templates/video_embed.mustache b/plugin/live/templates/video_embed.mustache index e96749a5..f0999a17 100644 --- a/plugin/live/templates/video_embed.mustache +++ b/plugin/live/templates/video_embed.mustache @@ -54,6 +54,7 @@ id="video-embed-{{uniqueid}}" autoplay controls {{# playsinline }} playsinline {{/ playsinline }} + muted >