mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
webrtc fallback image video snap.
This commit is contained in:
parent
8f8094e7bd
commit
33245fea03
@ -1,6 +1,7 @@
|
|||||||
import EventEmitter from 'eventemitter3'
|
import EventEmitter from 'eventemitter3'
|
||||||
import { WebRTCStats, CursorPosition, CursorImage } from '../types/webrtc'
|
import { WebRTCStats, CursorPosition, CursorImage } from '../types/webrtc'
|
||||||
import { Logger } from '../utils/logger'
|
import { Logger } from '../utils/logger'
|
||||||
|
import { videoSnap } from '../utils/video-snap'
|
||||||
|
|
||||||
export const OPCODE = {
|
export const OPCODE = {
|
||||||
MOVE: 0x01,
|
MOVE: 0x01,
|
||||||
@ -25,11 +26,15 @@ export interface NekoWebRTCEvents {
|
|||||||
negotiation: (description: RTCSessionDescriptionInit) => void
|
negotiation: (description: RTCSessionDescriptionInit) => void
|
||||||
stable: (isStable: boolean) => void
|
stable: (isStable: boolean) => void
|
||||||
stats: (stats: WebRTCStats) => void
|
stats: (stats: WebRTCStats) => void
|
||||||
|
fallback: (image: string) => void // send last frame image URL as fallback
|
||||||
['cursor-position']: (data: CursorPosition) => void
|
['cursor-position']: (data: CursorPosition) => void
|
||||||
['cursor-image']: (data: CursorImage) => void
|
['cursor-image']: (data: CursorImage) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NekoWebRTC extends EventEmitter<NekoWebRTCEvents> {
|
export class NekoWebRTC extends EventEmitter<NekoWebRTCEvents> {
|
||||||
|
// used for creating snaps from video for fallback mode
|
||||||
|
public video!: HTMLVideoElement
|
||||||
|
|
||||||
private _peer?: RTCPeerConnection
|
private _peer?: RTCPeerConnection
|
||||||
private _channel?: RTCDataChannel
|
private _channel?: RTCDataChannel
|
||||||
private _track?: MediaStreamTrack
|
private _track?: MediaStreamTrack
|
||||||
@ -248,6 +253,10 @@ export class NekoWebRTC extends EventEmitter<NekoWebRTCEvents> {
|
|||||||
throw new Error('attempting to close nonexistent peer')
|
throw new Error('attempting to close nonexistent peer')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create and emit video snap before closing connection
|
||||||
|
const imageSrc = await videoSnap(this.video)
|
||||||
|
this.emit('fallback', imageSrc)
|
||||||
|
|
||||||
this._peer.close()
|
this._peer.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<video ref="video" :autoplay="autoplay" :muted="autoplay" playsinline />
|
<video ref="video" :autoplay="autoplay" :muted="autoplay" playsinline />
|
||||||
<neko-screencast
|
<neko-screencast
|
||||||
v-show="screencast && screencastReady"
|
v-show="screencast && screencastReady"
|
||||||
|
:image="fallbackImage"
|
||||||
:enabled="screencast || !state.connection.webrtc.stable"
|
:enabled="screencast || !state.connection.webrtc.stable"
|
||||||
:api="api.room"
|
:api="api.room"
|
||||||
@imageReady="screencastReady = $event"
|
@imageReady="screencastReady = $event"
|
||||||
@ -100,6 +101,12 @@
|
|||||||
@Ref('container') readonly _container!: HTMLElement
|
@Ref('container') readonly _container!: HTMLElement
|
||||||
@Ref('video') readonly _video!: HTMLVideoElement
|
@Ref('video') readonly _video!: HTMLVideoElement
|
||||||
|
|
||||||
|
// fallback image for webrtc reconnections:
|
||||||
|
// chrome shows black screen when closing webrtc connection, that's why
|
||||||
|
// we need to grab video image before closing connection ans show that
|
||||||
|
// while reconnecting, to not see black screen
|
||||||
|
fallbackImage = ''
|
||||||
|
|
||||||
api = new NekoApi()
|
api = new NekoApi()
|
||||||
observer = new ResizeObserver(this.onResize.bind(this))
|
observer = new ResizeObserver(this.onResize.bind(this))
|
||||||
canvasSize: Dimension = { width: 0, height: 0 }
|
canvasSize: Dimension = { width: 0, height: 0 }
|
||||||
@ -433,6 +440,9 @@
|
|||||||
// component size change
|
// component size change
|
||||||
this.observer.observe(this._component)
|
this.observer.observe(this._component)
|
||||||
|
|
||||||
|
// webrtc needs video tag to capture video snaps for fallback mode
|
||||||
|
this.connection.webrtc.video = this._video
|
||||||
|
|
||||||
// video events
|
// video events
|
||||||
VideoRegister(this._video, this.state.video)
|
VideoRegister(this._video, this.state.video)
|
||||||
|
|
||||||
@ -441,6 +451,16 @@
|
|||||||
this.clear()
|
this.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// when webrtc emits fallback event, it means it is about to reconnect
|
||||||
|
// so we image that it provided (it is last frame of the video), we set
|
||||||
|
// it to the screencast module and pause video in order to show fallback
|
||||||
|
this.connection.webrtc.on('fallback', (image: string) => {
|
||||||
|
this.fallbackImage = image
|
||||||
|
|
||||||
|
// this ensures that fallback mode starts immediatly
|
||||||
|
this._video.pause()
|
||||||
|
})
|
||||||
|
|
||||||
this.connection.webrtc.on('track', (event: RTCTrackEvent) => {
|
this.connection.webrtc.on('track', (event: RTCTrackEvent) => {
|
||||||
const { track, streams } = event
|
const { track, streams } = event
|
||||||
if (track.kind === 'audio') return
|
if (track.kind === 'audio') return
|
||||||
|
@ -17,6 +17,14 @@
|
|||||||
private running = false
|
private running = false
|
||||||
private continue = false
|
private continue = false
|
||||||
|
|
||||||
|
@Prop()
|
||||||
|
private readonly image!: string
|
||||||
|
|
||||||
|
@Watch('image')
|
||||||
|
setImage(image: string) {
|
||||||
|
this.imageSrc = image
|
||||||
|
}
|
||||||
|
|
||||||
@Prop()
|
@Prop()
|
||||||
private readonly enabled!: boolean
|
private readonly enabled!: boolean
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user