diff --git a/src/components/WebRTCDebugConsole.vue b/src/components/WebRTCDebugConsole.vue new file mode 100644 index 0000000000..a0a5f1523a --- /dev/null +++ b/src/components/WebRTCDebugConsole.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/src/components/widgets/VideoPlayer.vue b/src/components/widgets/VideoPlayer.vue index 61bb3dd997..0b8c1bfcde 100644 --- a/src/components/widgets/VideoPlayer.vue +++ b/src/components/widgets/VideoPlayer.vue @@ -46,60 +46,98 @@ Your browser does not support the video tag. - - + + Video widget config - - - - Saved stream name: "{{ widget.options.internalStreamName }}" - - - -
- Rotate Left - Rotate Right + + +
+
+ + +
+ +
+ + + +
+ + +
+ Fit style + + + + + +
+
+ + +
+ + + + Reload stream + +
+ + + + + + + Debug console + + +
Select a stream to view debug logs.
+
+
+
@@ -110,6 +148,7 @@ import { storeToRefs } from 'pinia' import { computed, onBeforeMount, onBeforeUnmount, ref, toRefs, watch } from 'vue' import StatsForNerds from '@/components/VideoPlayerStatsForNerds.vue' +import WebRTCDebugConsole from '@/components/WebRTCDebugConsole.vue' import { useAppInterfaceStore } from '@/stores/appInterface' import { useVideoStore } from '@/stores/video' import { useWidgetManagerStore } from '@/stores/widgetManager' @@ -134,6 +173,11 @@ const nameSelectedStream = ref() const videoElement = ref() const mediaStream = ref() const streamConnected = ref(false) +const isReloading = ref(false) +const reloadProgress = ref(0) +const previousStream = ref() +const hasBeenMountedFor5Seconds = ref(false) +const debugPanelExpanded = ref() onBeforeMount(() => { // Set the default initial values that are not present in the widget options @@ -147,6 +191,11 @@ onBeforeMount(() => { } widget.value.options = Object.assign({}, defaultOptions, widget.value.options) nameSelectedStream.value = widget.value.options.internalStreamName + + // Set the 5-second delay before showing reload buttons + setTimeout(() => { + hasBeenMountedFor5Seconds.value = true + }, 5000) }) const externalStreamId = computed(() => { @@ -229,6 +278,34 @@ const rotateVideo = (angle: number): void => { widget.value.options.rotationAngle += angle } +const reloadVideo = (): void => { + if (isReloading.value || !nameSelectedStream.value) return + + // Store the current stream to re-select later + previousStream.value = nameSelectedStream.value + isReloading.value = true + reloadProgress.value = 0 + + // Clear the current stream + nameSelectedStream.value = undefined + + // Progress bar animation + const progressInterval = setInterval(() => { + reloadProgress.value += 100 / 30 // Update 30 times over 3 seconds + if (reloadProgress.value >= 100) { + clearInterval(progressInterval) + } + }, 100) + + // Re-select the stream after 3 seconds + setTimeout(() => { + nameSelectedStream.value = previousStream.value + isReloading.value = false + reloadProgress.value = 0 + clearInterval(progressInterval) + }, 3000) +} + const flipStyle = computed(() => { return `scale(${widget.value.options.flipHorizontally ? -1 : 1}, ${widget.value.options.flipVertically ? -1 : 1})` }) @@ -256,6 +333,12 @@ const streamStatus = computed(() => { } return videoStore.getStreamData(externalStreamId.value)?.webRtcManager.streamStatus ?? 'Unknown.' }) + +const debugStreamId = computed(() => { + if (!externalStreamId.value) return undefined + const streamData = videoStore.getStreamData(externalStreamId.value) + return streamData?.webRtcManager.getDebugStreamId() +}) diff --git a/src/composables/webRTC.ts b/src/composables/webRTC.ts index d91d956dd8..1cc62f16ab 100644 --- a/src/composables/webRTC.ts +++ b/src/composables/webRTC.ts @@ -2,6 +2,7 @@ import { type Ref, ref, watch } from 'vue' +import { useWebRTCDebugConsole } from '@/composables/webRTCDebugConsole' import * as Connection from '@/libs/connection/connection' import { Session } from '@/libs/webrtc/session' import { Signaller } from '@/libs/webrtc/signaller' @@ -46,6 +47,8 @@ export class WebRTCManager { private selectedICEIPs: string[] = [] private selectedICEProtocols: string[] = [] private JitterBufferTarget = 0 + private debugConsole = useWebRTCDebugConsole() + private instanceId: string private hasEnded = false private signaller: Signaller @@ -60,6 +63,7 @@ export class WebRTCManager { constructor(webRTCSignallingURI: Connection.URI, rtcConfiguration: RTCConfiguration) { console.debug('[WebRTC] Trying to connect to signalling server.') this.rtcConfiguration = rtcConfiguration + this.instanceId = `webrtc-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` this.signaller = new Signaller( webRTCSignallingURI, true, @@ -167,6 +171,10 @@ export class WebRTCManager { private updateStreamStatus(newStatus: string): void { const time = new Date().toTimeString().split(' ').first() this.streamStatus.value = `${newStatus} (${time})` + + // Add to debug console + const streamId = this.streamName || this.instanceId + this.debugConsole.addMessage(streamId, 'stream', newStatus) } /** @@ -176,6 +184,10 @@ export class WebRTCManager { private updateSignallerStatus(newStatus: string): void { const time = new Date().toTimeString().split(' ').first() this.signallerStatus.value = `${newStatus} (${time})` + + // Add to debug console + const streamId = this.streamName || this.instanceId + this.debugConsole.addMessage(streamId, 'signaller', newStatus) } /** @@ -269,6 +281,14 @@ export class WebRTCManager { } } + /** + * Get the debug stream ID for this manager instance + * @returns {string} The debug stream ID + */ + public getDebugStreamId(): string { + return this.streamName || this.instanceId + } + /** * * @param {Stream} stream diff --git a/src/composables/webRTCDebugConsole.ts b/src/composables/webRTCDebugConsole.ts new file mode 100644 index 0000000000..27f40e82c4 --- /dev/null +++ b/src/composables/webRTCDebugConsole.ts @@ -0,0 +1,78 @@ +import { ref } from 'vue' + +/** + * WebRTC debug message interface + */ +export interface WebRTCDebugMessage { + /** + * Timestamp of the message + */ + timestamp: Date + /** + * Stream ID of the message + */ + streamId: string + /** + * Type of the message + */ + type: 'signaller' | 'stream' + /** + * Message content + */ + message: string +} + +// Module-level state - shared across all consumers +const messages = ref<{ [streamId: string]: WebRTCDebugMessage[] }>({}) + +// Maximum number of messages to keep per stream (to prevent memory issues) +const maxMessagesPerStream = 1000 + +const addMessage = (streamId: string, type: 'signaller' | 'stream', message: string): void => { + if (!messages.value[streamId]) { + messages.value[streamId] = [] + } + + const debugMessage: WebRTCDebugMessage = { + timestamp: new Date(), + streamId, + type, + message, + } + + messages.value[streamId].push(debugMessage) + + // Keep only the most recent messages + if (messages.value[streamId].length > maxMessagesPerStream) { + messages.value[streamId] = messages.value[streamId].slice(-maxMessagesPerStream) + } +} + +const getMessagesForStream = (streamId: string): WebRTCDebugMessage[] => { + return messages.value[streamId] || [] +} + +const clearMessagesForStream = (streamId: string): void => { + if (messages.value[streamId]) { + messages.value[streamId] = [] + } +} + +const clearAllMessages = (): void => { + messages.value = {} +} + +/** + * Composable for WebRTC debug console functionality + * @returns {object} The WebRTC debug console functionality + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const useWebRTCDebugConsole = () => { + return { + messages, + addMessage, + getMessagesForStream, + clearMessagesForStream, + clearAllMessages, + } +}