Last active
November 9, 2019 01:45
-
-
Save bryanjswift/9637ed3042c3ff8ca109b49df60470b5 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* For a given `state` from `CompositeMachine` should the video indicated by | |
* `state.context.videoSrc` be in a playback state or a paused state. | |
* @param state node from the `CompositeMachine`. | |
* @returns `false` (indicating a playing state) or `true` (indicating a paused | |
* state). `null` or `undefined` state nodes are `true`. | |
*/ | |
function isVideoPaused(state) { | |
if (state === null || state === undefined) { | |
return true; | |
} | |
switch (state.value) { | |
case 'starting': | |
case 'startRecording': | |
case 'recording': | |
return false; | |
default: | |
return true; | |
} | |
} | |
/** | |
* State machine tracking the parallel state of the components that load | |
* asynchronously. Triggers a "done" state when each of the components have | |
* indicated readiness. This is sort of like `Promise.all` for the readiness of | |
* these components. | |
*/ | |
const CompositeComponentsLoadingMachine = Machine({ | |
id: 'composite-components-loading', | |
type: 'parallel', | |
states: { | |
pixi: { | |
type: 'compound', | |
initial: 'pending', | |
states: { | |
pending: { | |
on: { | |
PIXI_READY: { | |
target: 'success', | |
}, | |
}, | |
}, | |
success: { | |
type: 'final', | |
}, | |
}, | |
}, | |
video: { | |
type: 'compound', | |
initial: 'pending', | |
states: { | |
pending: { | |
on: { | |
VIDEO_READY: { | |
target: 'success', | |
}, | |
}, | |
}, | |
success: { | |
type: 'final', | |
}, | |
}, | |
}, | |
}, | |
}); | |
/** | |
* State machine tracking the state of the components that load asynchronously. | |
* Enters a "mounted" state when each of the components have indicated | |
* readiness and then transitions to a "done" state when `PIXI_INIT` is | |
* completed. | |
*/ | |
const CompositeComponentsMachine = Machine({ | |
id: 'composite-components', | |
initial: 'ssr', | |
states: { | |
ssr: { | |
invoke: { | |
id: 'components-loader', | |
src: CompositeComponentsLoadingMachine, | |
onDone: 'mounted', | |
}, | |
on: { | |
PIXI_READY: { | |
actions: [send('PIXI_READY', { to: 'components-loader' })], | |
}, | |
VIDEO_READY: { | |
actions: [send('VIDEO_READY', { to: 'components-loader' })], | |
}, | |
}, | |
}, | |
mounted: { | |
on: { | |
PIXI_INIT: { | |
target: 'ready', | |
}, | |
}, | |
}, | |
ready: { | |
type: 'final', | |
}, | |
}, | |
}); | |
/** | |
* Track the state of compositing through the lifetime of page setup and | |
* recorded composite. | |
*/ | |
const CompositeMachine = Machine( | |
{ | |
id: 'composite', | |
initial: 'ssr', | |
context: { | |
audioStream: undefined, // MediaStream | |
canvasDisplay: undefined, // CanvasDisplay - Svelte Component Instance | |
error: undefined, | |
framesData: undefined, // Record from getFramesData | |
recorder: undefined, // VideoRecorderService | |
sourceVideo: undefined, // Video|Canvas Element - Used for compositing | |
videoData: undefined, // Blob | |
videoSrc: undefined, // string | |
}, | |
states: { | |
ssr: { | |
on: { | |
MOUNT: { | |
target: 'mounted', | |
actions: ['setSources'], | |
}, | |
}, | |
}, | |
mounted: { | |
on: { | |
NO_MEDIA: { | |
target: 'missingMedia', | |
}, | |
GET_FRAMES: { | |
target: 'getFrames', | |
cond: 'hasFramesId', | |
}, | |
GET_MEDIA: { | |
target: 'getMedia', | |
cond: 'hasVideoId', | |
}, | |
}, | |
}, | |
getFrames: { | |
invoke: { | |
id: 'getFrames', | |
src: (context, event) => getFramesData(event.framesId), | |
onDone: { | |
target: 'pending', | |
actions: ['setFramesContext'], | |
}, | |
onError: { | |
target: 'missingMedia', | |
actions: ['setError'], | |
}, | |
}, | |
}, | |
getMedia: { | |
invoke: { | |
id: 'getMedia', | |
src: (context, event) => getVideoBlob(event.videoId), | |
onDone: { | |
target: 'pending', | |
actions: ['setVideoContext'], | |
}, | |
onError: { | |
target: 'missingMedia', | |
actions: ['setError'], | |
}, | |
}, | |
}, | |
missingMedia: { | |
on: { | |
GET_FRAMES: { | |
target: 'getFrames', | |
cond: 'hasFramesId', | |
}, | |
GET_MEDIA: { | |
target: 'getMedia', | |
cond: 'hasVideoId', | |
}, | |
}, | |
}, | |
pending: { | |
on: { | |
COMPONENTS_READY: { | |
target: 'preloading', | |
actions: ['setRecorder'], | |
}, | |
}, | |
}, | |
preloading: { | |
on: { | |
ANIMATION_READY: 'ready', | |
}, | |
}, | |
ready: { | |
on: { | |
GET_MEDIA: 'getMedia', | |
PLAY: 'starting', | |
}, | |
}, | |
starting: { | |
on: { | |
RECORD: { | |
actions: ['setRecorder'], | |
target: 'startRecording', | |
}, | |
}, | |
}, | |
startRecording: { | |
invoke: { | |
id: 'startRecording', | |
src: (context, event) => { | |
const { recorder } = context; | |
return recorder.startRecording(); | |
}, | |
onDone: { | |
target: 'recording', | |
}, | |
onError: { | |
target: 'recordError', | |
actions: ['setError'], | |
}, | |
}, | |
}, | |
recordError: { | |
type: 'final', | |
}, | |
recording: { | |
on: { | |
STOP: { | |
target: 'awaitingFrames', | |
actions: ['stopRecording'], | |
}, | |
}, | |
}, | |
awaitingFrames: { | |
on: { | |
FRAMES: { | |
target: 'framesSetup', | |
actions: ['setFramesContext'], | |
}, | |
}, | |
}, | |
framesSetup: { | |
entry: ['disposeRecorder'], | |
on: { | |
FRAMES_WAIT: { | |
target: 'framesRecordWaiting', | |
actions: ['setRecorder'], | |
}, | |
}, | |
}, | |
framesRecordWaiting: { | |
on: { | |
FRAMES_PLAY: 'framesRecordStarting', | |
}, | |
}, | |
framesRecordStarting: { | |
on: { | |
FRAMES_RECORD: 'framesRecord', | |
}, | |
}, | |
framesRecord: { | |
// This state is only reachable if the source data is a video | |
invoke: { | |
id: 'startRecording', | |
src: (context, event) => { | |
const { recorder } = context; | |
const { canvasDisplay, sourceVideo } = event; | |
const videoStream = sourceVideo.mozCaptureStream | |
? sourceVideo.mozCaptureStream() | |
: sourceVideo.captureStream(); | |
const canvasStream = canvasDisplay.canvas.mozCaptureStream | |
? canvasDisplay.canvas.mozCaptureStream() | |
: canvasDisplay.canvas.captureStream(); | |
canvasDisplay.play(); | |
sourceVideo.play(); | |
return recorder.startRecording(canvasStream, videoStream); | |
}, | |
onDone: { | |
target: 'framesRecording', | |
}, | |
onError: { | |
target: 'recordError', | |
actions: ['setError'], | |
}, | |
}, | |
}, | |
framesRecording: { | |
on: { | |
FRAMES_STOP: { | |
target: 'ready', | |
actions: ['stopRecording'], | |
}, | |
}, | |
}, | |
}, | |
}, | |
{ | |
actions: { | |
disposeRecorder(context) { | |
if (context.recorder) { | |
context.recorder.dispose(); | |
} | |
}, | |
log(context, event) { | |
console.log('actions:log', context, event); | |
}, | |
setError: assign({ error: (context, event) => event.data }), | |
setFramesContext: assign({ framesData: (context, event) => event.data }), | |
setRecorder: assign({ | |
recorder: (context, event) => event.recorder || context.recorder, | |
}), | |
setSources: assign({ | |
audioStream: (context, event) => event.audioStream, | |
canvasDisplay: (context, event) => event.canvasDisplay, | |
sourceVideo: (context, event) => event.sourceVideo, | |
}), | |
setVideoContext: assign({ | |
videoData: (context, event) => event.data, | |
videoSrc: (context, event) => URL.createObjectURL(event.data), | |
}), | |
stopRecording(context, event) { | |
context.recorder.stopRecording(); | |
}, | |
}, | |
guards: { | |
hasFramesId(context, event) { | |
return typeof event.framesId === 'string'; | |
}, | |
hasRecorder(context, event) { | |
return typeof context.recorder !== 'undefined'; | |
}, | |
hasVideoId(context, event) { | |
return typeof event.videoId === 'string'; | |
}, | |
}, | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment