-
Notifications
You must be signed in to change notification settings - Fork 40
Webrtc refactor #138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gjmooney
wants to merge
7
commits into
maartenbreddels:master
Choose a base branch
from
gjmooney:webrtc_refactor
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Webrtc refactor #138
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
734919e
Split out stream classes
gjmooney 14d6eab
Update init import
gjmooney 223627a
Add util import to Image
gjmooney 845c58a
Start adding pytest testing
gjmooney 7e20ef0
Add class
gjmooney 5dc36b1
CI
gjmooney 1ade720
Update test
gjmooney File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,7 @@ share | |
|
||
# OS X | ||
.DS_Store | ||
.yarn/ | ||
.vscode | ||
labextension | ||
yarn.lock | ||
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export * from "./streams/Audio"; | ||
export * from "./streams/Camera"; | ||
export * from "./streams/Image"; | ||
export * from "./streams/Media"; | ||
export * from "./streams/Recorder"; | ||
export * from "./streams/Video"; | ||
export * from "./streams/Webrtc"; | ||
export * from "./streams/Widget"; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,17 @@ | ||
const jupyter_webrtc = require("./index"); | ||
const base = require("@jupyter-widgets/base"); | ||
import { IJupyterWidgetRegistry } from "@jupyter-widgets/base"; | ||
import { version } from "../package.json"; | ||
|
||
module.exports = { | ||
const extension = { | ||
id: "jupyter-webrtc", | ||
requires: [base.IJupyterWidgetRegistry], | ||
activate: function (app, widgets) { | ||
requires: [IJupyterWidgetRegistry], | ||
activate: (app, widgets) => { | ||
widgets.registerWidget({ | ||
name: "jupyter-webrtc", | ||
version: jupyter_webrtc.version, | ||
exports: jupyter_webrtc, | ||
version: version, | ||
exports: async () => import("./index"), | ||
}); | ||
}, | ||
autoStart: true, | ||
}; | ||
|
||
export default extension; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { DOMWidgetView, unpack_models } from "@jupyter-widgets/base"; | ||
import { RecorderModel, RecorderView } from "./Recorder"; | ||
import { StreamModel } from "./Media"; | ||
|
||
export class AudioStreamModel extends StreamModel { | ||
defaults() { | ||
return { | ||
...super.defaults(), | ||
_model_name: "AudioStreamModel", | ||
_view_name: "AudioStreamView", | ||
audio: undefined, | ||
}; | ||
} | ||
|
||
initialize() { | ||
super.initialize.apply(this, arguments); | ||
window.last_audio_stream = this; | ||
|
||
this.type = "audio"; | ||
} | ||
} | ||
|
||
AudioStreamModel.serializers = { | ||
...StreamModel.serializers, | ||
audio: { deserialize: unpack_models }, | ||
}; | ||
|
||
export class AudioStreamView extends DOMWidgetView { | ||
render() { | ||
super.render.apply(this, arguments); | ||
window.last_audio_stream_view = this; | ||
this.audio = document.createElement("audio"); | ||
this.audio.controls = true; | ||
this.pWidget.addClass("jupyter-widgets"); | ||
|
||
this.model.captureStream().then( | ||
(stream) => { | ||
this.audio.srcObject = stream; | ||
this.el.appendChild(this.audio); | ||
this.audio.play(); | ||
}, | ||
(error) => { | ||
const text = document.createElement("div"); | ||
text.innerHTML = | ||
"Error creating view for mediastream: " + error.message; | ||
this.el.appendChild(text); | ||
}, | ||
); | ||
} | ||
|
||
remove() { | ||
this.model.captureStream().then((stream) => { | ||
this.audio.pause(); | ||
this.audio.srcObject = null; | ||
}); | ||
return super.remove.apply(this, arguments); | ||
} | ||
} | ||
|
||
export class AudioRecorderModel extends RecorderModel { | ||
defaults() { | ||
return { | ||
...super.defaults(), | ||
_model_name: "AudioRecorderModel", | ||
_view_name: "AudioRecorderView", | ||
audio: null, | ||
}; | ||
} | ||
|
||
initialize() { | ||
super.initialize.apply(this, arguments); | ||
window.last_audio_recorder = this; | ||
|
||
this.type = "audio"; | ||
} | ||
} | ||
|
||
AudioRecorderModel.serializers = { | ||
...RecorderModel.serializers, | ||
audio: { deserialize: unpack_models }, | ||
}; | ||
|
||
export class AudioRecorderView extends RecorderView { | ||
initialize() { | ||
super.initialize.apply(this, arguments); | ||
this.tag = "audio"; | ||
this.recordIconClass = "fa fa-circle"; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { MediaStreamModel } from "./Media"; | ||
|
||
export class CameraStreamModel extends MediaStreamModel { | ||
defaults() { | ||
return { | ||
...super.defaults(), | ||
_model_name: "CameraStreamModel", | ||
constraints: { audio: true, video: true }, | ||
}; | ||
} | ||
|
||
captureStream() { | ||
if (!this.cameraStream) { | ||
this.cameraStream = navigator.mediaDevices.getUserMedia( | ||
this.get("constraints"), | ||
); | ||
} | ||
return this.cameraStream; | ||
} | ||
|
||
close() { | ||
if (this.cameraStream) { | ||
this.cameraStream.then((stream) => { | ||
stream.getTracks().forEach((track) => { | ||
track.stop(); | ||
}); | ||
}); | ||
} | ||
return super.close.apply(this, arguments); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import { unpack_models } from "@jupyter-widgets/base"; | ||
import * as utils from "../utils"; | ||
import { imageWidgetToCanvas } from "../utils"; | ||
import { MediaStreamModel } from "./Media"; | ||
import { RecorderModel, RecorderView } from "./Recorder"; | ||
|
||
const captureStream = function (widget) { | ||
if (widget.captureStream) { | ||
return widget.captureStream(); | ||
} else { | ||
return widget.stream; | ||
} | ||
}; | ||
|
||
export class ImageStreamModel extends MediaStreamModel { | ||
defaults() { | ||
return { | ||
...super.defaults(), | ||
_model_name: "ImageStreamModel", | ||
image: null, | ||
}; | ||
} | ||
|
||
initialize() { | ||
super.initialize.apply(this, arguments); | ||
window.last_image_stream = this; | ||
this.canvas = document.createElement("canvas"); | ||
this.context = this.canvas.getContext("2d"); | ||
|
||
this.canvas.width = this.get("width"); | ||
this.canvas.height = this.get("height"); | ||
// I was hoping this should do it | ||
imageWidgetToCanvas(this.get("image"), this.canvas); | ||
this.get("image").on("change:value", this.sync_image, this); | ||
} | ||
|
||
sync_image() { | ||
// not sure if firefox uses moz prefix also on a canvas | ||
if (this.canvas.captureStream) { | ||
// TODO: add a fps trait | ||
// but for some reason we need to do it again | ||
imageWidgetToCanvas(this.get("image"), this.canvas); | ||
} else { | ||
throw new Error("captureStream not supported for this browser"); | ||
} | ||
} | ||
|
||
async captureStream() { | ||
this.sync_image(); | ||
return this.canvas.captureStream(); | ||
} | ||
} | ||
|
||
ImageStreamModel.serializers = { | ||
...MediaStreamModel.serializers, | ||
image: { deserialize: unpack_models }, | ||
}; | ||
|
||
export class ImageRecorderModel extends RecorderModel { | ||
defaults() { | ||
return { | ||
...super.defaults(), | ||
_model_name: "ImageRecorderModel", | ||
_view_name: "ImageRecorderView", | ||
image: null, | ||
_height: "", | ||
_width: "", | ||
}; | ||
} | ||
|
||
initialize() { | ||
super.initialize.apply(this, arguments); | ||
window.last_image_recorder = this; | ||
|
||
this.type = "image"; | ||
} | ||
|
||
async snapshot() { | ||
const mimeType = this.type + "/" + this.get("format"); | ||
const mediaStream = await captureStream(this.get("stream")); | ||
// turn the mediastream into a video element | ||
let video = document.createElement("video"); | ||
video.srcObject = mediaStream; | ||
video.play(); | ||
await utils.onCanPlay(video); | ||
await utils.onLoadedMetaData(video); | ||
// and the video element can be drawn onto a canvas | ||
let canvas = document.createElement("canvas"); | ||
let context = canvas.getContext("2d"); | ||
let height = video.videoHeight; | ||
let width = video.videoWidth; | ||
canvas.height = height; | ||
canvas.width = width; | ||
context.drawImage(video, 0, 0, canvas.width, canvas.height); | ||
|
||
// from the canvas we can get the underlying encoded data | ||
// TODO: check support for toBlob, or find a polyfill | ||
const blob = await utils.canvasToBlob(canvas, mimeType); | ||
this.set("_data_src", window.URL.createObjectURL(blob)); | ||
this._last_blob = blob; | ||
|
||
const bytes = await utils.blobToBytes(blob); | ||
|
||
this.get(this.type).set("value", new DataView(bytes.buffer)); | ||
this.get(this.type).save_changes(); | ||
this.set("_height", height.toString() + "px"); | ||
this.set("_width", width.toString() + "px"); | ||
this.set("recording", false); | ||
this.save_changes(); | ||
} | ||
|
||
updateRecord() { | ||
const source = this.get("stream"); | ||
if (!source) { | ||
throw new Error("No stream specified"); | ||
} | ||
|
||
if (this.get("_data_src") !== "") { | ||
URL.revokeObjectURL(this.get("_data_src")); | ||
} | ||
if (this.get("recording")) this.snapshot(); | ||
} | ||
|
||
download() { | ||
let filename = this.get("filename"); | ||
let format = this.get("format"); | ||
if (filename.indexOf(".") < 0) { | ||
filename = this.get("filename") + "." + format; | ||
} | ||
utils.downloadBlob(this._last_blob, filename); | ||
} | ||
} | ||
|
||
ImageRecorderModel.serializers = { | ||
...RecorderModel.serializers, | ||
image: { deserialize: unpack_models }, | ||
}; | ||
|
||
export class ImageRecorderView extends RecorderView { | ||
initialize() { | ||
super.initialize.apply(this, arguments); | ||
this.tag = "img"; | ||
this.recordIconClass = "fa fa-camera"; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.