Skip to content

Commit 8952e39

Browse files
committed
chore: Update to latest Vue version and build tools (#11)
* Update to vite, use latest vue tools Remove comment Update babel and package-lock Fix lint issues Fix lint issues Fix prettier Add github action Test before building * Lint ts files too * Add tsconfig Lint ts files too Add tsconfig Add tsconfig Use vanilla js in Vue does not work Still not working Add console log for vue mounted Gate off and blocked instead of playable THIS WORKS. Move div back into Vue. THIS WORKS Fix spacing
1 parent 147f6f9 commit 8952e39

File tree

3 files changed

+260
-37
lines changed

3 files changed

+260
-37
lines changed

index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
>
1616
</noscript>
1717
<div id="app"></div>
18-
<script type="module" src="/src/main.js"></script>
18+
<script type="module" src="/src/main.ts"></script>
1919
</body>
2020
</html>

src/components/CallTile.vue

+259-36
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,7 @@
2626
</template>
2727

2828
<div v-if="participants" class="participants-container">
29-
<template v-for="p in participants" :key="p.session_id">
30-
<video-tile
31-
:participant="p"
32-
:handle-video-click="handleVideoClick"
33-
:handle-audio-click="handleAudioClick"
34-
:handle-screenshare-click="handleScreenshareClick"
35-
:leave-call="leaveAndCleanUp"
36-
:disable-screen-share="screen && !screen?.local"
37-
/>
38-
</template>
29+
<div id="video-call"></div>
3930

4031
<template v-if="count === 1">
4132
<waiting-card :url="roomUrl" />
@@ -50,8 +41,13 @@
5041
</main>
5142
</template>
5243

53-
<script>
54-
import daily from "@daily-co/daily-js";
44+
<script lang="ts">
45+
import { defineComponent } from "vue";
46+
47+
import daily, {
48+
type DailyCall,
49+
type DailyParticipant,
50+
} from "@daily-co/daily-js";
5551
5652
import WaitingCard from "./WaitingCard.vue";
5753
import ChatTile from "./ChatTile.vue";
@@ -60,7 +56,33 @@ import ScreenshareTile from "./ScreenshareTile.vue";
6056
import LoadingTile from "./LoadingTile.vue";
6157
import PermissionsErrorMsg from "./PermissionsErrorMsg.vue";
6258
63-
export default {
59+
interface Participant {
60+
session_id: string;
61+
user_id: string;
62+
user_name: string;
63+
local: boolean;
64+
video: boolean;
65+
audio: boolean;
66+
screen: boolean;
67+
}
68+
69+
interface CallTileData {
70+
callObject: null | DailyCall;
71+
loading: boolean;
72+
error: boolean;
73+
showPermissionsError: boolean;
74+
participants: Participant[];
75+
screen: unknown; // video track
76+
messages: string[];
77+
count: number;
78+
}
79+
80+
type Tracks = {
81+
videoTrack: MediaStreamTrack | null;
82+
audioTrack: MediaStreamTrack | null;
83+
};
84+
85+
export default defineComponent({
6486
name: "CallTile",
6587
components: {
6688
VideoTile,
@@ -70,11 +92,24 @@ export default {
7092
LoadingTile,
7193
PermissionsErrorMsg,
7294
},
73-
props: ["leaveCall", "name", "roomUrl"],
74-
data() {
95+
props: {
96+
leaveCall: {
97+
type: Function,
98+
required: true,
99+
},
100+
name: {
101+
type: String,
102+
required: true,
103+
},
104+
roomUrl: {
105+
type: String,
106+
required: true,
107+
},
108+
},
109+
data(): CallTileData {
75110
return {
76111
callObject: null,
77-
participants: null,
112+
participants: [],
78113
count: 0,
79114
messages: [],
80115
error: false,
@@ -98,31 +133,219 @@ export default {
98133
99134
// Add call and participant event handler
100135
// Visit https://docs.daily.co/reference/daily-js/events for more event info
101-
co.on("joining-meeting", this.handleJoiningMeeting)
102-
.on("joined-meeting", this.updateParticpants)
103-
.on("participant-joined", this.updateParticpants)
104-
.on("participant-updated", this.updateParticpants)
105-
.on("participant-left", this.updateParticpants)
136+
co
137+
// .on("joining-meeting", this.handleJoiningMeeting)
138+
// .on("joined-meeting", this.updateParticpants)
139+
// .on("participant-joined", this.updateParticpants)
140+
// .on("participant-updated", this.updateParticpants)
141+
// .on("participant-left", this.updateParticpants)
106142
.on("error", this.handleError)
107143
// camera-error = device permissions issue
108144
.on("camera-error", this.handleDeviceError)
109145
// app-message handles receiving remote chat messages
110-
.on("app-message", this.updateMessages);
146+
.on("app-message", this.updateMessages)
147+
.on("track-started", (p) => {
148+
if (!p?.participant) return;
149+
const tracks = this.getParticipantTracks(p.participant);
150+
try {
151+
this.updateMedia(p.participant.session_id, tracks);
152+
} catch (e) {
153+
console.warn(e);
154+
}
155+
})
156+
.on("track-stopped", (p) => {
157+
if (!p?.participant) return;
158+
const tracks = this.getParticipantTracks(p.participant);
159+
try {
160+
this.updateMedia(p.participant.session_id, tracks);
161+
} catch (e) {
162+
console.warn(e);
163+
}
164+
});
111165
},
112166
unmounted() {
113167
if (!this.callObject) return;
114168
// Clean-up event handlers
115169
this.callObject
116-
.off("joining-meeting", this.handleJoiningMeeting)
117-
.off("joined-meeting", this.updateParticpants)
118-
.off("participant-joined", this.updateParticpants)
119-
.off("participant-updated", this.updateParticpants)
120-
.off("participant-left", this.updateParticpants)
170+
// .off("joining-meeting", this.handleJoiningMeeting)
171+
// .off("joined-meeting", this.updateParticpants)
172+
// .off("participant-joined", this.updateParticpants)
173+
// .off("participant-updated", this.updateParticpants)
174+
// .off("participant-left", this.updateParticpants)
121175
.off("error", this.handleError)
122176
.off("camera-error", this.handleDeviceError)
123-
.off("app-message", this.updateMessages);
177+
.off("app-message", this.updateMessages)
178+
.off("track-started", (p) => {
179+
if (!p?.participant) return;
180+
const tracks = this.getParticipantTracks(p.participant);
181+
try {
182+
this.updateMedia(p.participant.session_id, tracks);
183+
} catch (e) {
184+
console.warn(e);
185+
}
186+
})
187+
.off("track-stopped", (p) => {
188+
if (!p?.participant) return;
189+
const tracks = this.getParticipantTracks(p.participant);
190+
try {
191+
this.updateMedia(p.participant.session_id, tracks);
192+
} catch (e) {
193+
console.warn(e);
194+
}
195+
});
124196
},
125197
methods: {
198+
updateMedia(participantID: string, newTracks: Tracks) {
199+
// Get the video tag.
200+
let videoTile = document.getElementById(
201+
participantID
202+
) as HTMLVideoElement | null;
203+
if (!videoTile) {
204+
const videoCall = document.getElementById(
205+
"video-call"
206+
) as HTMLDivElement;
207+
const newVideoTile = document.createElement("video");
208+
newVideoTile.id = participantID;
209+
videoCall.appendChild(newVideoTile);
210+
videoTile = newVideoTile;
211+
}
212+
213+
const video = videoTile;
214+
215+
// Get existing MediaStream from the video tag source object.
216+
const existingStream = video.srcObject as MediaStream;
217+
218+
const newVideo = newTracks.videoTrack;
219+
const newAudio = newTracks.audioTrack;
220+
221+
// If there is no existing stream or it contains no tracks,
222+
// Just create a new media stream using our new tracks.
223+
// This will happen if this is the first time we're
224+
// setting the tracks.
225+
if (!existingStream || existingStream.getTracks().length === 0) {
226+
const tracks: MediaStreamTrack[] = [];
227+
if (newVideo) tracks.push(newVideo);
228+
if (newAudio) tracks.push(newAudio);
229+
const newStream = new MediaStream(tracks);
230+
video.srcObject = newStream;
231+
video.playsInline = true;
232+
video.autoplay = true;
233+
video.muted = true;
234+
this.playMedia(video);
235+
return;
236+
}
237+
238+
// This boolean will define whether we play the video element again
239+
// This should be `true` if any of the tracks have changed.
240+
let needsPlay = false;
241+
needsPlay = this.refreshAudioTrack(existingStream, newAudio);
242+
243+
// We have an extra if check here compared to the audio track
244+
// handling above, because the video track also dictates
245+
// whether we should hide the video DOM element.
246+
if (newVideo) {
247+
if (this.refreshVideoTrack(existingStream, newVideo) && !needsPlay) {
248+
needsPlay = true;
249+
}
250+
251+
video.classList.remove("hidden");
252+
} else {
253+
// If there's no video to be played, hide the element.
254+
video.classList.add("hidden");
255+
}
256+
if (needsPlay) {
257+
this.playMedia(video);
258+
}
259+
},
260+
refreshAudioTrack(
261+
existingStream: MediaStream,
262+
newAudioTrack: MediaStreamTrack | null
263+
): boolean {
264+
// If there is no new track, just early out
265+
// and keep the old track on the stream as-is.
266+
if (!newAudioTrack) return false;
267+
const existingTracks = existingStream.getAudioTracks();
268+
return this.refreshTrack(existingStream, existingTracks, newAudioTrack);
269+
},
270+
refreshVideoTrack(
271+
existingStream: MediaStream,
272+
newVideoTrack: MediaStreamTrack | null
273+
): boolean {
274+
// If there is no new track, just early out
275+
// and keep the old track on the stream as-is.
276+
if (!newVideoTrack) return false;
277+
const existingTracks = existingStream.getVideoTracks();
278+
return this.refreshTrack(existingStream, existingTracks, newVideoTrack);
279+
},
280+
refreshTrack(
281+
existingStream: MediaStream,
282+
oldTracks: MediaStreamTrack[],
283+
newTrack: MediaStreamTrack
284+
): boolean {
285+
const trackCount = oldTracks.length;
286+
// If there is no matching old track,
287+
// just add the new track.
288+
if (trackCount === 0) {
289+
existingStream.addTrack(newTrack);
290+
return true;
291+
}
292+
if (trackCount > 1) {
293+
console.warn(
294+
`expected up to 1 media track, but got ${trackCount}. Only using the first one.`
295+
);
296+
}
297+
const oldTrack = oldTracks[0];
298+
// If the IDs of the old and new track don't match,
299+
// replace the old track with the new one.
300+
if (oldTrack.id !== newTrack.id) {
301+
existingStream.removeTrack(oldTrack);
302+
existingStream.addTrack(newTrack);
303+
return true;
304+
}
305+
return false;
306+
},
307+
playMedia(video: HTMLVideoElement) {
308+
const isPlaying =
309+
!video.paused &&
310+
!video.ended &&
311+
video.currentTime > 0 &&
312+
video.readyState > video.HAVE_CURRENT_DATA;
313+
314+
if (isPlaying) return;
315+
316+
video.play().catch((e) => {
317+
if (e instanceof Error && e.name === "NotAllowedError") {
318+
throw new Error("Autoplay error");
319+
}
320+
321+
console.warn("Failed to play media.", e);
322+
});
323+
},
324+
getParticipantTracks(p: DailyParticipant) {
325+
const mediaTracks: Tracks = {
326+
videoTrack: null,
327+
audioTrack: null,
328+
};
329+
330+
const tracks = p?.tracks;
331+
if (!tracks) return mediaTracks;
332+
333+
const vt = tracks.video;
334+
const vs = vt?.state;
335+
if (vt.persistentTrack && !(vs === "off" || vs === "blocked")) {
336+
mediaTracks.videoTrack = vt.persistentTrack;
337+
}
338+
339+
// Only get audio track if this is a remote participant
340+
if (!p.local) {
341+
const at = tracks.audio;
342+
const as = at?.state;
343+
if (at.persistentTrack && !(as === "off" || as === "blocked")) {
344+
mediaTracks.audioTrack = at.persistentTrack;
345+
}
346+
}
347+
return mediaTracks;
348+
},
126349
/**
127350
* This is called any time a participant update registers.
128351
* In large calls, this should be optimized to avoid re-renders.
@@ -163,13 +386,13 @@ export default {
163386
},
164387
// Toggle local microphone in use (on/off)
165388
handleAudioClick() {
166-
const audioOn = this.callObject.localAudio();
167-
this.callObject.setLocalAudio(!audioOn);
389+
const audioOn = this.callObject?.localAudio();
390+
this.callObject?.setLocalAudio(!audioOn);
168391
},
169392
// Toggle local camera in use (on/off)
170393
handleVideoClick() {
171-
const videoOn = this.callObject.localVideo();
172-
this.callObject.setLocalVideo(!videoOn);
394+
const videoOn = this.callObject?.localVideo();
395+
this.callObject?.setLocalVideo(!videoOn);
173396
},
174397
// Show permissions error in UI to alert local participant
175398
handleDeviceError() {
@@ -178,10 +401,10 @@ export default {
178401
// Toggle screen share
179402
handleScreenshareClick() {
180403
if (this.screen?.local) {
181-
this.callObject.stopScreenShare();
404+
this.callObject?.stopScreenShare();
182405
this.screen = null;
183406
} else {
184-
this.callObject.startScreenShare();
407+
this.callObject?.startScreenShare();
185408
}
186409
},
187410
/**
@@ -192,7 +415,7 @@ export default {
192415
*/
193416
sendMessage(text) {
194417
// Attach the local participant's username to the message to be displayed in ChatTile.vue
195-
const local = this.callObject.participants().local;
418+
const local = this.callObject?.participants().local;
196419
const message = { message: text, name: local?.user_name || "Guest" };
197420
this.messages.push(message);
198421
this.callObject.sendAppMessage(message, "*");
@@ -211,7 +434,7 @@ export default {
211434
});
212435
},
213436
},
214-
};
437+
});
215438
</script>
216439

217440
<style scoped>

src/main.js src/main.ts

File renamed without changes.

0 commit comments

Comments
 (0)