openvidu-vue: Migrated to Livekit
This commit is contained in:
parent
79afe21997
commit
0aaaa8b354
11536
openvidu-vue/package-lock.json
generated
11536
openvidu-vue/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,9 +8,9 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.21.1",
|
||||
"core-js": "3.9.1",
|
||||
"openvidu-browser": "2.27.0",
|
||||
"axios": "1.5.1",
|
||||
"core-js": "3.33.0",
|
||||
"livekit-client": "1.14.0",
|
||||
"vue": "2.6.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -229,6 +229,19 @@ video {
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.participant-name {
|
||||
position: absolute;
|
||||
background: #f8f8f8;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
color: #777777;
|
||||
font-weight: bold;
|
||||
border-bottom-right-radius: 4px;
|
||||
z-index: 1000;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* xs ans md screen resolutions*/
|
||||
|
||||
|
||||
@ -1,201 +1,279 @@
|
||||
<template>
|
||||
<div id="main-container" class="container">
|
||||
<div id="join" v-if="!session">
|
||||
<div id="img-div">
|
||||
<img src="resources/images/openvidu_grey_bg_transp_cropped.png" />
|
||||
</div>
|
||||
<div id="join-dialog" class="jumbotron vertical-center">
|
||||
<h1>Join a video session</h1>
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<label>Participant</label>
|
||||
<input v-model="myUserName" class="form-control" type="text" required />
|
||||
</p>
|
||||
<p>
|
||||
<label>Session</label>
|
||||
<input v-model="mySessionId" class="form-control" type="text" required />
|
||||
</p>
|
||||
<p class="text-center">
|
||||
<button class="btn btn-lg btn-success" @click="joinSession()">
|
||||
Join!
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-container" class="container">
|
||||
<div id="join" v-if="!room">
|
||||
<div id="img-div">
|
||||
<img src="resources/images/openvidu_grey_bg_transp_cropped.png" />
|
||||
</div>
|
||||
<div id="join-dialog" class="jumbotron vertical-center">
|
||||
<h1>Join a video room</h1>
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<label>Participant</label>
|
||||
<input
|
||||
v-model="myParticipantName"
|
||||
class="form-control"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<label>Session</label>
|
||||
<input
|
||||
v-model="myRoomName"
|
||||
class="form-control"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
</p>
|
||||
<p class="text-center">
|
||||
<button class="btn btn-lg btn-success" @click="joinRoom()">
|
||||
Join!
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="session" v-if="session">
|
||||
<div id="session-header">
|
||||
<h1 id="session-title">{{ mySessionId }}</h1>
|
||||
<input class="btn btn-large btn-danger" type="button" id="buttonLeaveSession" @click="leaveSession"
|
||||
value="Leave session" />
|
||||
</div>
|
||||
<div id="main-video" class="col-md-6">
|
||||
<user-video :stream-manager="mainStreamManager" />
|
||||
</div>
|
||||
<div id="video-container" class="col-md-6">
|
||||
<user-video :stream-manager="publisher" @click.native="updateMainVideoStreamManager(publisher)" />
|
||||
<user-video v-for="sub in subscribers" :key="sub.stream.connection.connectionId" :stream-manager="sub"
|
||||
@click.native="updateMainVideoStreamManager(sub)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="room" v-if="room">
|
||||
<div id="room-header">
|
||||
<h1 id="room-title">{{ myRoomName }}</h1>
|
||||
<input
|
||||
class="btn btn-large btn-danger"
|
||||
type="button"
|
||||
id="buttonLeaveRoom"
|
||||
@click="leaveRoom"
|
||||
value="Leave room"
|
||||
/>
|
||||
</div>
|
||||
<div id="main-video" class="col-md-6">
|
||||
<p v-if="mainPublication" class="participant-name">{{ getParticipantName(mainPublication.trackSid) }}</p>
|
||||
<OvVideo
|
||||
v-if="mainPublication && mainPublication.videoTrack"
|
||||
:track="mainPublication.videoTrack"
|
||||
/>
|
||||
</div>
|
||||
<div id="video-container" class="col-md-6">
|
||||
<p v-if="localPublication && localPublication.videoTrack" class="participant-name">{{ myParticipantName }}</p>
|
||||
<OvVideo
|
||||
v-if="localPublication && localPublication.videoTrack"
|
||||
:track="localPublication.videoTrack"
|
||||
@click.native="updateMainPublication(localPublication)"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-for="publication in remotePublications"
|
||||
:key="publication.trackSid"
|
||||
>
|
||||
<p v-if="publication.videoTrack" class="participant-name">
|
||||
{{ getParticipantName(publication.trackSid) }}
|
||||
</p>
|
||||
|
||||
<OvVideo
|
||||
v-if="publication.videoTrack"
|
||||
:track="publication.videoTrack"
|
||||
@click.native="updateMainPublication(publication)"
|
||||
/>
|
||||
<OvAudio
|
||||
v-if="publication.audioTrack"
|
||||
:track="publication.audioTrack"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import { OpenVidu } from "openvidu-browser";
|
||||
import UserVideo from "./components/UserVideo";
|
||||
import axios from 'axios';
|
||||
|
||||
axios.defaults.headers.post["Content-Type"] = "application/json";
|
||||
import OvVideo from './components/OvVideo';
|
||||
import OvAudio from './components/OvAudio';
|
||||
import { Room, RoomEvent } from 'livekit-client';
|
||||
|
||||
const APPLICATION_SERVER_URL = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:5000/';
|
||||
axios.defaults.headers.post['Content-Type'] = 'application/json';
|
||||
|
||||
const APPLICATION_SERVER_URL = 'http://localhost:5000/';
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
name: 'App',
|
||||
|
||||
components: {
|
||||
UserVideo,
|
||||
},
|
||||
components: {
|
||||
OvVideo,
|
||||
OvAudio
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// OpenVidu objects
|
||||
OV: undefined,
|
||||
session: undefined,
|
||||
mainStreamManager: undefined,
|
||||
publisher: undefined,
|
||||
subscribers: [],
|
||||
data() {
|
||||
return {
|
||||
// OpenVidu objects
|
||||
room: undefined,
|
||||
mainPublication: undefined,
|
||||
localPublication: undefined,
|
||||
remotePublications: [],
|
||||
|
||||
// Join form
|
||||
mySessionId: "SessionA",
|
||||
myUserName: "Participant" + Math.floor(Math.random() * 100),
|
||||
};
|
||||
},
|
||||
// Join form
|
||||
myRoomName: 'RoomA',
|
||||
myParticipantName: 'Participant' + Math.floor(Math.random() * 100),
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
joinSession() {
|
||||
// --- 1) Get an OpenVidu object ---
|
||||
this.OV = new OpenVidu();
|
||||
methods: {
|
||||
joinRoom() {
|
||||
// --- 1) Init a room ---
|
||||
this.room = new Room();
|
||||
|
||||
// --- 2) Init a session ---
|
||||
this.session = this.OV.initSession();
|
||||
// --- 2) Specify the actions when events take place in the room ---
|
||||
|
||||
// --- 3) Specify the actions when events take place in the session ---
|
||||
// On every new Track received...
|
||||
this.room.on(
|
||||
RoomEvent.TrackSubscribed,
|
||||
(track, publication, participant) => {
|
||||
console.log('Track subscribed', track, publication, participant);
|
||||
// Store the new publication in remotePublications array
|
||||
this.remotePublications.push(publication);
|
||||
}
|
||||
);
|
||||
|
||||
// On every new Stream received...
|
||||
this.session.on("streamCreated", ({ stream }) => {
|
||||
const subscriber = this.session.subscribe(stream);
|
||||
this.subscribers.push(subscriber);
|
||||
});
|
||||
// On every track destroyed...
|
||||
this.room.on(
|
||||
RoomEvent.TrackUnsubscribed,
|
||||
(track, publication, participant) => {
|
||||
console.log('Track unsubscribed', track, publication, participant);
|
||||
// Remove the publication from 'remotePublications' array
|
||||
this.deleteRemoteTrackPublication(publication);
|
||||
}
|
||||
);
|
||||
|
||||
// On every Stream destroyed...
|
||||
this.session.on("streamDestroyed", ({ stream }) => {
|
||||
const index = this.subscribers.indexOf(stream.streamManager, 0);
|
||||
if (index >= 0) {
|
||||
this.subscribers.splice(index, 1);
|
||||
}
|
||||
});
|
||||
// --- 3) Connect to the room with a valid access token ---
|
||||
|
||||
// On every asynchronous exception...
|
||||
this.session.on("exception", ({ exception }) => {
|
||||
console.warn(exception);
|
||||
});
|
||||
// Get a token from the application backend
|
||||
this.getToken(this.myRoomName, this.myParticipantName).then((token) => {
|
||||
const livekitUrl = this.getLivekitUrlFromMetadata(token);
|
||||
// First param is the token. Second param can be retrieved by every user on event
|
||||
// 'streamCreated' (property Stream.connection.data), and will be appended to DOM as the user's nickname
|
||||
this.room
|
||||
.connect(livekitUrl, token)
|
||||
.then(async () => {
|
||||
// --- 4) Publish your local tracks ---
|
||||
await this.room.localParticipant.setMicrophoneEnabled(true);
|
||||
const videoPublication = await this.room.localParticipant.setCameraEnabled(true);
|
||||
|
||||
// --- 4) Connect to the session with a valid user token ---
|
||||
// Set the main video in the page to display our webcam and store our localPublication
|
||||
this.localPublication = videoPublication;
|
||||
this.mainPublication = videoPublication;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(
|
||||
'There was an error connecting to the room:',
|
||||
error.code,
|
||||
error.message
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Get a token from the OpenVidu deployment
|
||||
this.getToken(this.mySessionId).then((token) => {
|
||||
window.addEventListener('beforeunload', this.leaveRoom);
|
||||
},
|
||||
|
||||
// First param is the token. Second param can be retrieved by every user on event
|
||||
// 'streamCreated' (property Stream.connection.data), and will be appended to DOM as the user's nickname
|
||||
this.session.connect(token, { clientData: this.myUserName })
|
||||
.then(() => {
|
||||
leaveRoom() {
|
||||
// --- 7) Leave the room by calling 'disconnect' method over the Session object ---
|
||||
if (this.room) this.room.disconnect();
|
||||
|
||||
// --- 5) Get your own camera stream with the desired properties ---
|
||||
// Empty all properties...
|
||||
this.room = undefined;
|
||||
this.mainPublication = undefined;
|
||||
this.localPublication = undefined;
|
||||
this.remotePublications = [];
|
||||
// Remove beforeunload listener
|
||||
window.removeEventListener('beforeunload', this.leaveRoom);
|
||||
},
|
||||
|
||||
// Init a publisher passing undefined as targetElement (we don't want OpenVidu to insert a video
|
||||
// element: we will manage it on our own) and with the desired properties
|
||||
let publisher = this.OV.initPublisher(undefined, {
|
||||
audioSource: undefined, // The source of audio. If undefined default microphone
|
||||
videoSource: undefined, // The source of video. If undefined default webcam
|
||||
publishAudio: true, // Whether you want to start publishing with your audio unmuted or not
|
||||
publishVideo: true, // Whether you want to start publishing with your video enabled or not
|
||||
resolution: "640x480", // The resolution of your video
|
||||
frameRate: 30, // The frame rate of your video
|
||||
insertMode: "APPEND", // How the video is inserted in the target element 'video-container'
|
||||
mirror: false, // Whether to mirror your local video or not
|
||||
});
|
||||
getParticipantName(trackSid) {
|
||||
if(!this.localPublication) return;
|
||||
const isLocalTrack = trackSid === this.localPublication.trackSid;
|
||||
|
||||
// Set the main video in the page to display our webcam and store our Publisher
|
||||
this.mainStreamManager = publisher;
|
||||
this.publisher = publisher;
|
||||
if (isLocalTrack) {
|
||||
// Return local participant name
|
||||
return this.myParticipantName;
|
||||
}
|
||||
|
||||
// --- 6) Publish your stream ---
|
||||
// Find in remote participants the participant with the track and return his name
|
||||
const remoteParticipant = Array.from(
|
||||
this.room.participants.values()
|
||||
).find((p) => {
|
||||
return p.getTracks().some((t) => t.trackSid === trackSid);
|
||||
});
|
||||
return remoteParticipant?.identity;
|
||||
},
|
||||
|
||||
this.session.publish(this.publisher);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("There was an error connecting to the session:", error.code, error.message);
|
||||
});
|
||||
});
|
||||
updateMainPublication(publication) {
|
||||
this.mainPublication = publication;
|
||||
},
|
||||
|
||||
window.addEventListener("beforeunload", this.leaveSession);
|
||||
},
|
||||
deleteRemoteTrackPublication(publication) {
|
||||
let index = this.remotePublications.findIndex(
|
||||
(p) => p.trackSid === publication.trackSid
|
||||
);
|
||||
if (index > -1) {
|
||||
this.remotePublications.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
leaveSession() {
|
||||
// --- 7) Leave the session by calling 'disconnect' method over the Session object ---
|
||||
if (this.session) this.session.disconnect();
|
||||
|
||||
// Empty all properties...
|
||||
this.session = undefined;
|
||||
this.mainStreamManager = undefined;
|
||||
this.publisher = undefined;
|
||||
this.subscribers = [];
|
||||
this.OV = undefined;
|
||||
getLivekitUrlFromMetadata(token) {
|
||||
if (!token) throw new Error('Trying to get metadata from an empty token');
|
||||
try {
|
||||
const base64Url = token.split('.')[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const jsonPayload = decodeURIComponent(
|
||||
window
|
||||
.atob(base64)
|
||||
.split('')
|
||||
.map((c) => {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
})
|
||||
.join('')
|
||||
);
|
||||
|
||||
// Remove beforeunload listener
|
||||
window.removeEventListener("beforeunload", this.leaveSession);
|
||||
},
|
||||
const payload = JSON.parse(jsonPayload);
|
||||
if (!payload?.metadata) throw new Error('Token does not contain metadata');
|
||||
const metadata = JSON.parse(payload.metadata);
|
||||
return metadata.livekitUrl;
|
||||
} catch (error) {
|
||||
throw new Error('Error decoding and parsing token: ' + error);
|
||||
}
|
||||
},
|
||||
|
||||
updateMainVideoStreamManager(stream) {
|
||||
if (this.mainStreamManager === stream) return;
|
||||
this.mainStreamManager = stream;
|
||||
},
|
||||
|
||||
/**
|
||||
* --------------------------------------------
|
||||
* GETTING A TOKEN FROM YOUR APPLICATION SERVER
|
||||
* --------------------------------------------
|
||||
* The methods below request the creation of a Session and a Token to
|
||||
* your application server. This keeps your OpenVidu deployment secure.
|
||||
*
|
||||
* In this sample code, there is no user control at all. Anybody could
|
||||
* access your application server endpoints! In a real production
|
||||
* environment, your application server must identify the user to allow
|
||||
* access to the endpoints.
|
||||
*
|
||||
* Visit https://docs.openvidu.io/en/stable/application-server to learn
|
||||
* more about the integration of OpenVidu in your application server.
|
||||
*/
|
||||
async getToken(mySessionId) {
|
||||
const sessionId = await this.createSession(mySessionId);
|
||||
return await this.createToken(sessionId);
|
||||
},
|
||||
|
||||
async createSession(sessionId) {
|
||||
const response = await axios.post(APPLICATION_SERVER_URL + 'api/sessions', { customSessionId: sessionId }, {
|
||||
headers: { 'Content-Type': 'application/json', },
|
||||
});
|
||||
return response.data; // The sessionId
|
||||
},
|
||||
|
||||
async createToken(sessionId) {
|
||||
const response = await axios.post(APPLICATION_SERVER_URL + 'api/sessions/' + sessionId + '/connections', {}, {
|
||||
headers: { 'Content-Type': 'application/json', },
|
||||
});
|
||||
return response.data; // The token
|
||||
},
|
||||
},
|
||||
/**
|
||||
* --------------------------------------------
|
||||
* GETTING A TOKEN FROM YOUR APPLICATION SERVER
|
||||
* --------------------------------------------
|
||||
* The methods below request the creation of a Token to
|
||||
* your application server. This keeps your OpenVidu deployment secure.
|
||||
*
|
||||
* In this sample code, there is no user control at all. Anybody could
|
||||
* access your application server endpoints! In a real production
|
||||
* environment, your application server must identify the user to allow
|
||||
* access to the endpoints.
|
||||
*
|
||||
* Visit https://docs.openvidu.io/en/stable/application-server to learn
|
||||
* more about the integration of OpenVidu in your application server.
|
||||
*/
|
||||
async getToken(roomName, participantName) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
APPLICATION_SERVER_URL + 'token',
|
||||
{ roomName, participantName },
|
||||
{
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
responseType: 'text',
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
// Handle errors here
|
||||
console.error('Error getting token:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
17
openvidu-vue/src/components/OvAudio.vue
Normal file
17
openvidu-vue/src/components/OvAudio.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<audio playsinline/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'OvAudio',
|
||||
|
||||
props: {
|
||||
track: Object,
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.track.attach(this.$el);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -7,11 +7,11 @@ export default {
|
||||
name: 'OvVideo',
|
||||
|
||||
props: {
|
||||
streamManager: Object,
|
||||
track: Object,
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.streamManager.addVideoElement(this.$el);
|
||||
this.track.attach(this.$el);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<div v-if="streamManager">
|
||||
<ov-video :stream-manager="streamManager"/>
|
||||
<div><p>{{ clientData }}</p></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OvVideo from './OvVideo';
|
||||
|
||||
export default {
|
||||
name: 'UserVideo',
|
||||
|
||||
components: {
|
||||
OvVideo,
|
||||
},
|
||||
|
||||
props: {
|
||||
streamManager: Object,
|
||||
},
|
||||
|
||||
computed: {
|
||||
clientData () {
|
||||
const { clientData } = this.getConnectionData();
|
||||
return clientData;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getConnectionData () {
|
||||
const { connection } = this.streamManager.stream;
|
||||
return JSON.parse(connection.data);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Loading…
x
Reference in New Issue
Block a user