You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
projectx/src/lib/VueJanus.vue

786 lines
35 KiB

<template>
<v-container fill-height>
<PdfShare v-if="sharePdf" :streamer="streamer" :username="username" :opaque-id="opaqueId" :room="1234" :janus-init="janusInit" ></PdfShare>
<v-dialog
v-model="mozillaAlert"
max-width="600"
>
<v-card>
<v-card-title class="headline">Share whole screen or a window?</v-card-title>
<v-card-text>
Firefox handles screensharing in a different way: are you going to share the whole screen, or would you rather pick a single window/application to share instead?
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="shareScreenStart('screen')">Share a screen</v-btn>
<v-spacer></v-spacer>
<v-btn @click="shareScreenStart('window')">Share a window</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="chromeAlert"
max-width="600"
>
<v-card>
<v-card-title class="headline">Chrome Extension Error</v-card-title>
<v-card-text>
You're using Chrome but don't have the screensharing extension installed: click <b><a href='https://chrome.google.com/webstore/detail/janus-webrtc-screensharin/hapfgfdkleiggjjpfpenajgdnfckjpaj' target='_blank'>here</a></b> to do so
</v-card-text>
</v-card>
</v-dialog>
<v-dialog
v-model="deviceSelectDialog"
max-width="290"
>
<v-card>
<v-card-title class="headline">Configuration</v-card-title>
<v-card-text>
<v-row>
<v-col md="12" cols="12">
<v-select
label="Please select video device"
v-model="selectedVideo"
:items="videoDevices"
item-value="deviceId"
item-text="label"
></v-select>
<v-checkbox label="No Video" v-model="noVid"></v-checkbox>
</v-col>
<v-col md="12" cols="12">
<v-select
label="Please select audio device"
:items="audioDevices"
v-model="selectedAudio"
item-value="deviceId"
item-text="label"
></v-select>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="green darken-1"
text
@click="deviceSelectDialog = false"
>
Disagree
</v-btn>
<v-btn
color="green darken-1"
text
@click="selectDevice"
>
Agree
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<div v-show="remoteViewNew">
<div style="width: 100%;height: 100%;background: #ccc;position: absolute;top:0;left:0"></div>
<v-row>
<v-col cols="12" md="10"></v-col>
<v-col cols="12" md="2">
<v-row>
<v-col cols="12" md="12" v-for="remoteStream in remoteStreams" :key="remoteStream.id">
<RemoteFeed v-if="remoteViewNew" @kick="kickUser" @rtp="$emit('rtp',remoteStream.id)" :creator="creator" :talking="remoteStream.talking" :opaqueId="opaqueId" :mypvtid="mypvtid" :feedid="remoteStream.id" :remote-stream="remoteStream" :janusInit="janusInit" :room="1234" ></RemoteFeed>
</v-col>
</v-row>
</v-col>
</v-row>
</div>
<v-row v-show="!remoteViewNew">
<v-col cols="12" :md="mdsize">
<div width="100%" height="100%" style="position: relative">
<video ref="ownstream" v-if="streamer" class="rounded centered" id="myvideo" width="100%" height="100%" autoplay
playsinline muted="muted"/>
<video ref="ownstream" v-if="!streamer" class="rounded centered" id="myvideo" width="100%" height="100%" autoplay
playsinline muted="muted"/>
<v-btn icon v-if="ownmuted" @click="muteMe"><v-icon>mdi-volume-mute</v-icon></v-btn>
<v-btn icon v-if="!ownmuted" @click="muteMe"><v-icon>mdi-volume-high</v-icon></v-btn>
</div>
</v-col>
<v-col cols="12" :md="mdsize">
<video ref="ownstreamscreen" class="rounded centered" id="myvideo2" width="100%" height="100%" autoplay
playsinline muted="muted"/>
</v-col>
<v-col cols="12" :md="mdsize" v-for="remoteStream in remoteStreams" :key="remoteStream.id">
<RemoteFeed v-if="!remoteViewNew" @kick="kickUser" @rtp="$emit('rtp',remoteStream.id)" :creator="creator" :talking="remoteStream.talking" :opaqueId="opaqueId" :mypvtid="mypvtid" :feedid="remoteStream.id" :remote-stream="remoteStream" :janusInit="janusInit" :room="1234" ></RemoteFeed>
</v-col>
</v-row>
<v-speed-dial
v-model="fab"
bottom
left
direction="top"
open-on-hover
>
<template v-slot:activator>
<v-btn
v-model="fab"
color="blue darken-2"
dark
fab
>
<v-icon v-if="fab">mdi-cog-outline</v-icon>
<v-icon v-else>mdi-cog-outline</v-icon>
</v-btn>
</template>
<v-tooltip right>
<template v-slot:activator="{ on }">
<v-btn
v-on="on"
fab
dark
small
@click="sharePdf=true"
>
<v-icon>mdi-pdf-box</v-icon>
</v-btn>
</template>
<span>Toggle Share PDF</span>
</v-tooltip>
<v-tooltip right>
<template v-slot:activator="{ on }">
<v-btn
v-on="on"
fab
dark
small
@click="$vuetify.theme.dark=!$vuetify.theme.dark"
>
<v-icon>mdi-compare</v-icon>
</v-btn>
</template>
<span>Toggle Theme</span>
</v-tooltip>
<v-tooltip right>
<template v-slot:activator="{ on }">
<v-btn
v-on="on"
fab
dark
small
@click="remoteViewNew = !remoteViewNew"
>
<v-icon>mdi-compare</v-icon>
</v-btn>
</template>
<span>Toggle View</span>
</v-tooltip>
<v-tooltip right>
<template v-slot:activator="{ on }">
<v-btn
v-on="on"
fab
dark
small
@click="deviceSelectDialog = true"
>
<v-icon>mdi-camera-outline</v-icon>
</v-btn>
</template>
<span>Toggle Camera</span>
</v-tooltip>
<v-tooltip right>
<template v-slot:activator="{ on }">
<v-btn
v-on="on"
fab
:dark="!screenShareStarted"
small
@click="screenShareSwitch"
>
<v-icon>mdi-monitor-screenshot</v-icon>
</v-btn>
</template>
<span>Toggle Share Screen</span>
</v-tooltip>
</v-speed-dial>
</v-container>
</template>
<script>
import RemoteFeed from "@/lib/RemoteFeed";
import PdfShare from "@/lib/PdfShare";
import WebRTCAdaptor from "@/lib/webrtc_adaptor";
export default {
components: {PdfShare, RemoteFeed},
props: {
server: String,
room: Number,
stream: Boolean,
username: String,
creator: Boolean,
streamer: Boolean
},
data() {
return {
janusInit: null,
antwrtc: null,
opaqueId: null,
pluginHandle: null,
mystream: null,
myid: '',
mdsize:6,
ownmuted: false,
mypvtid: '',
selectedVideo: null,
selectedAudio: null,
started: false,
mozillaAlert: false,
sharePdf: false,
remoteViewNew: false,
janusScreenShareHandle: null,
chromeAlert: false,
videoDevices: [],
capture: '',
audioDevices: [],
deviceSelectDialog: false,
noVid: false,
fab: true,
shareScreenId: null,
remoteStreams: [],
screenShareStarted: false,
antwrtcScreen: false
}
},
name: "VueJanus",
mounted() {
this.janusInit = new this.$janus({
success: this.janusSuccess,
server: this.server,
error: function (error) {
this.$janus.error(error);
},
webrtcState: this.webrtcState,
destroyed: function () {
window.location.reload();
}
})
},
methods: {
kickUser (user) {
this.$emit('kick',user)
},
muteMe () {
this.ownmuted = !this.ownmuted
if(!this.ownmuted) {
this.pluginHandle.unmuteAudio()
} else {
this.pluginHandle.muteAudio()
}
},
stopSharePdf(){
this.sharePdf=false;
},
webrtcState (on) {
this.pluginHandle.send({"message": { "request": "configure", "bitrate": 128*1000 }});
console.log(on, "webrt")
},
webrtcState2 (on) {
this.pluginHandle.send({"message": { "request": "configure", "bitrate": 128*1000 }});
console.log(on, "webrt")
//this.antwrtc.publish(this.username, "null");
},
janusPluginSuccessScreen (pluginHandle) {
this.janusScreenShareHandle = pluginHandle
this.$janus.log("Plugin attached! (" + this.pluginHandle.getPlugin() + ", id=" + this.pluginHandle.getId() + ")");
this.$janus.log(" -- This is a publisher/manager");
var register = { "request": "join", "room": 1234, "ptype": "publisher", "display": this.username + " - Share Screen" };
pluginHandle.send({"message": register});
},
castStart(jsep) {
var publish = { "request": "configure", "audio": false, "video": true };
this.janusScreenShareHandle.send({"message": publish, "jsep": jsep});
},
streamerScreenShare(stream) {
let websocketURL = "wss://live.ozgurkon.org/LiveApp/websocket"
var mediaConstraints = {
video: { width: 1280, height: 720 },
audio: false
};
var pc_config = null;
var sdpConstraints = {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
};
if (this.streamer) {
var tt = this
this.antwrtcScreen = new WebRTCAdaptor({
websocket_url: websocketURL,
mediaConstraints: mediaConstraints,
peerconnection_config: pc_config,
sdp_constraints: sdpConstraints,
janusScreen:stream,
localVideoId: "myvideo2",
debug: true,
callback: function (info, obj) {
if (info == "initialized") {
console.log("initialized");
tt.antwrtcScreen.publish(tt.username+"-desktop", "null");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info === "publish_started") {
//stream is being published
console.log("publish started");
// start_publish_button.disabled = true;
// stop_publish_button.disabled = false;
// startAnimation();
} else if (info == "publish_finished") {
//stream is being finished
console.log("publish finished");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info == "screen_share_extension_available") {
// screen_share_checkbox.disabled = false;
// console.log("screen share extension available");
// install_extension_link.style.display = "none";
} else if (info == "screen_share_stopped") {
console.log("screen share stopped");
} else if (info == "closed") {
//console.log("Connection closed");
if (typeof obj != "undefined") {
console.log("Connecton closed: " + JSON.stringify(obj));
}
} else if (info == "pong") {
//ping/pong message are sent to and received from server to make the connection alive all the time
//It's especially useful when load balancer or firewalls close the websocket connection due to inactivity
} else if (info == "refreshConnection") {
this.startPublishing();
} else if (info == "updated_stats") {
//obj is the PeerStats which has fields
//averageOutgoingBitrate - kbits/sec
//currentOutgoingBitrate - kbits/sec
console.log("Average outgoing bitrate " + obj.averageOutgoingBitrate + " kbits/sec"
+ " Current outgoing bitrate: " + obj.currentOutgoingBitrate + " kbits/sec");
}
},
callbackError: function (error, message) {
//some of the possible errors, NotFoundError, SecurityError,PermissionDeniedError
console.log("error callback: " + JSON.stringify(error));
var errorMessage = JSON.stringify(error);
if (typeof message != "undefined") {
errorMessage = message;
}
if (error.indexOf("NotFoundError") != -1) {
errorMessage = "Camera or Mic are not found or not allowed in your device";
} else if (error.indexOf("NotReadableError") != -1 || error.indexOf("TrackStartError") != -1) {
errorMessage = "Camera or Mic is being used by some other process that does not let read the devices";
} else if (error.indexOf("OverconstrainedError") != -1 || error.indexOf("ConstraintNotSatisfiedError") != -1) {
errorMessage = "There is no device found that fits your video and audio constraints. You may change video and audio constraints"
} else if (error.indexOf("NotAllowedError") != -1 || error.indexOf("PermissionDeniedError") != -1) {
errorMessage = "You are not allowed to access camera and mic.";
} else if (error.indexOf("TypeError") != -1) {
errorMessage = "Video/Audio is required";
}
alert(errorMessage);
}
});
}
},
janusMessageScreen (msg, jesp) {
var event = msg["videoroom"]
console.log(jesp)
if(event === "joined") {
this.janusScreenShareHandle.createOffer(
{
media: { video: this.capture, audioSend: false, videoRecv: false}, // Screen sharing Publishers are sendonly
simulcast:true,
simulcast2:true,
success: this.castStart,
error: function(error) {
console.log("WebRTC error:", error);
// bootbox.alert("WebRTC error... " + JSON.stringify(error));
}
});
}
if(jesp !== undefined && jesp !== null) {
// Janus.debug("Handling SDP as well...");
// Janus.debug(jsep);
this.janusScreenShareHandle.handleRemoteJsep({jsep: jesp});
}
},
onlocalstreamScreen (stream) {
if(this.screenShareStarted)
this.screenShareStarted=true;
this.streamerScreenShare(stream)
this.$refs.ownstreamscreen.srcObject = stream
},
screenShareSwitch () {
if(this.screenShareStarted) {
this.screenShareStop()
} else {
this.screenShare()
}
},
shareScreenStart (capture) {
this.capture = capture
this.janusInit.attach(
{
plugin: "janus.plugin.videoroom",
opaqueId: this.opaqueId,
success: this.janusPluginSuccessScreen,
consentDialog: this.consentDialog,
error: this.janusPluginError,
mediaState: this.mediaState,
webrtcState: this.webrtcState2,
onmessage: this.janusMessageScreen,
onlocalstream: this.onlocalstreamScreen
}
)
this.mozillaAlert=false;
this.chromeAlert=false;
},
screenShareStop(){
var srcObject=this.$refs.ownstreamscreen.srcObject;
for (const track of srcObject.getTracks()) {
track.stop();
}
var unpublish = { request: 'unpublish' };
this.janusScreenShareHandle.send({ message: unpublish });
this.janusScreenShareHandle.detach();
this.screenShareStarted=false;
},
screenShare () {
if(!this.$janus.isExtensionEnabled()) {
this.chromeAlert = true
return;
}
if(navigator.mozGetUserMedia) {
// Firefox needs a different constraint for screen and window sharing
this.mozillaAlert = true
return;
}
this.shareScreenStart('screen')
},
janusPluginError (error) {
console.log(error)
},
consentDialog (on) {
// this.$janus.debug("Consent dialog should be " + (on ? "on" : "off") + " now");
console.log(on)
},
janusPluginSuccess (pluginHandle) {
this.pluginHandle = pluginHandle
this.$janus.log("Plugin attached! (" + this.pluginHandle.getPlugin() + ", id=" + this.pluginHandle.getId() + ")");
this.$janus.log(" -- This is a publisher/manager");
this.$janus.listDevices(this.initDevices);
},
initDevices (devices) {
console.log(devices)
devices.forEach(device => {
if (device.kind === 'videoinput') {
this.videoDevices.push(device)
}
if (device.kind === 'audioinput') {
this.audioDevices.push(device)
}
})
this.deviceSelectDialog = true
},
genRemoteFeed () {
this.remoteStreams.forEach(item => {
this.createRemoteFeed(item)
})
},
offerSend (jsep) {
console.log('vid offer')
console.log(jsep)
var publish = {"request": "configure", "audio": true, "video": true};
this.pluginHandle.send({"message": publish, "jsep": jsep});
},
controlPublishers(msg) {
if (msg["publishers"].length > 12) {
this.mdsize = 1
} else {
var size = parseInt(18 / (msg["publishers"].length+1))
if(size<6) {
this.mdsize = size
} else {
this.mdsize = size /2
}
if (this.mdsize > 6) this.mdsize = 6
this.mdsize = parseInt(this.mdsize)
}
msg["publishers"].forEach(item => {
var has = false
item.talking = false
this.remoteStreams.forEach(rm => {
if (item.id === rm.id) has = true
})
if (!has) {
if (this.username + " - Share Screen" !== item.display) this.remoteStreams.push(item)
}
})
},
janusMessage (msg, jsep) {
console.log(msg);
var event = msg["videoroom"];
if (event !== undefined) {
if (event === "talking") {
this.remoteStreams.forEach(item => {
if (item.id === msg['id']) item.talking = true
})
}
if (event === "stopped-talking") {
this.remoteStreams.forEach(item => {
if (item.id === msg['id']) item.talking = false
})
}
if (event === "joined") {
this.myid = msg["id"];
this.mypvtid = msg["private_id"];
this.$janus.log("Successfully joined room " + msg["room"] + " with ID " + this.myid);
this.restartDevices()
if (msg["publishers"] !== undefined && msg["publishers"] !== null) {
this.controlPublishers(msg)
}
} else if(event === "event") {
if (msg["publishers"] !== undefined && msg["publishers"] !== null) {
this.controlPublishers(msg)
} else if(msg["leaving"] !== undefined && msg["leaving"] !== null) {
console.log("leaving")
this.remoteStreams.forEach(item => {
if (item.id === msg['leaving']) this.remoteStreams.splice(this.remoteStreams.indexOf(item),1)
})
}
}
}
if (jsep !== undefined && jsep !== null) {
console.log("Handling SDP as well...");
console.log(jsep);
this.pluginHandle.handleRemoteJsep({jsep: jsep});
var audio = msg["audio_codec"];
if (this.mystream && this.mystream.getAudioTracks() && this.mystream.getAudioTracks().length > 0 && !audio) {
console.log("Our audio stream has been rejected, viewers won't hear us");
}
}
},
onlocalstream (stream) {
this.mystream = stream
console.log(this.$refs)
console.log(this.$refs["ownstream"])
this.$refs.ownstream.srcObject = this.mystream
},
restartDevices () {
this.deviceSelectDialog = false
var media = {
audioRecv: false, videoRecv: false, audioSend: true, videoSend: !this.noVid,
simulcast:true,
simulcast2:true,
audio: {
deviceId: {
exact: this.selectedAudio
}
},
// replaceAudio: true, // This is only needed in case of a renegotiation
video: {
deviceId: {
exact: this.selectedVideo,
width:1280,
height: 720
},
width:1280,
height: 720
},
// replaceVideo: true,
};
this.pluginHandle.createOffer(
{
media: media,
success: this.offerSend,
error: function (error) {
// An error has occurred ...
console.log(error)
}
})
},
mediaState (medium, on) {
this.$janus.log("Janus " + (on ? "started" : "stopped") + " receiving our " + medium);
},
changeDevice () {
this.$refs.ownstream.setSinkId(this.selectedVideo)
this.$refs.ownstream.setSinkId(this.selectedAudio)
},
selectDevice: function () {
if (!this.started) {
console.log(this.selectedVideo)
console.log(this.selectedAudio)
this.started = true
var register = {
"request": "join",
"room": 1234,
"ptype": "publisher",
"display": this.username
};
this.pluginHandle.send({"message": register});
let websocketURL = "wss://live.ozgurkon.org/LiveApp/websocket"
var mediaConstraints = {
video: { width: 1280, height: 720 },
audio: true
};
var pc_config = null;
var sdpConstraints = {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
};
if (this.streamer) {
this.antwrtc = new WebRTCAdaptor({
websocket_url: websocketURL,
mediaConstraints: mediaConstraints,
peerconnection_config: pc_config,
sdp_constraints: sdpConstraints,
localVideoId: "myvideo",
debug: true,
callback: function (info, obj) {
if (info == "initialized") {
console.log("initialized");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info == "publish_started") {
//stream is being published
console.log("publish started");
// start_publish_button.disabled = true;
// stop_publish_button.disabled = false;
// startAnimation();
} else if (info == "publish_finished") {
//stream is being finished
console.log("publish finished");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info == "screen_share_extension_available") {
// screen_share_checkbox.disabled = false;
// console.log("screen share extension available");
// install_extension_link.style.display = "none";
} else if (info == "screen_share_stopped") {
console.log("screen share stopped");
} else if (info == "closed") {
//console.log("Connection closed");
if (typeof obj != "undefined") {
console.log("Connecton closed: " + JSON.stringify(obj));
}
} else if (info == "pong") {
//ping/pong message are sent to and received from server to make the connection alive all the time
//It's especially useful when load balancer or firewalls close the websocket connection due to inactivity
} else if (info == "refreshConnection") {
this.startPublishing();
} else if (info == "updated_stats") {
//obj is the PeerStats which has fields
//averageOutgoingBitrate - kbits/sec
//currentOutgoingBitrate - kbits/sec
console.log("Average outgoing bitrate " + obj.averageOutgoingBitrate + " kbits/sec"
+ " Current outgoing bitrate: " + obj.currentOutgoingBitrate + " kbits/sec");
}
},
callbackError: function (error, message) {
//some of the possible errors, NotFoundError, SecurityError,PermissionDeniedError
console.log("error callback: " + JSON.stringify(error));
var errorMessage = JSON.stringify(error);
if (typeof message != "undefined") {
errorMessage = message;
}
if (error.indexOf("NotFoundError") != -1) {
errorMessage = "Camera or Mic are not found or not allowed in your device";
} else if (error.indexOf("NotReadableError") != -1 || error.indexOf("TrackStartError") != -1) {
errorMessage = "Camera or Mic is being used by some other process that does not let read the devices";
} else if (error.indexOf("OverconstrainedError") != -1 || error.indexOf("ConstraintNotSatisfiedError") != -1) {
errorMessage = "There is no device found that fits your video and audio constraints. You may change video and audio constraints"
} else if (error.indexOf("NotAllowedError") != -1 || error.indexOf("PermissionDeniedError") != -1) {
errorMessage = "You are not allowed to access camera and mic.";
} else if (error.indexOf("TypeError") != -1) {
errorMessage = "Video/Audio is required";
}
alert(errorMessage);
}
});
}
} else {
this.restartDevices()
}
},
janusSuccess () {
this.opaqueId = "videoroomtest-" + this.$janus.randomString(12);
this.janusInit.attach(
{
plugin: "janus.plugin.videoroom",
opaqueId: this.opaqueId,
success: this.janusPluginSuccess,
consentDialog: this.consentDialog,
error: this.janusPluginError,
mediaState: this.mediaState,
webrtcState: this.webrtcState,
onmessage: this.janusMessage,
onlocalstream: this.onlocalstream
}
)
}
}
}
</script>
<style scoped>
</style>