diff --git a/ipywebrtc/webrtc.py b/ipywebrtc/webrtc.py index b6ce13a..e6f9c95 100644 --- a/ipywebrtc/webrtc.py +++ b/ipywebrtc/webrtc.py @@ -7,10 +7,12 @@ from urllib.request import urlopen # py3 from traitlets import ( observe, - Bool, Bytes, Dict, Instance, Int, List, TraitError, Unicode, validate, + Bool, Bytes, Dict, Instance, Int, List, TraitError, CUnicode, Unicode, validate, Undefined ) -from ipywidgets import DOMWidget, Image, Video, Audio, register, widget_serialization +from ipywidgets import ( + DOMWidget, Image, Video, Audio, register, widget_serialization, link +) from ipython_genutils.py3compat import string_types import ipywebrtc._version import traitlets @@ -83,6 +85,8 @@ class ImageStream(MediaStream): Image, help="An ipywidgets.Image instance that will be the source of the media stream." ).tag(sync=True, **widget_serialization) + width = CUnicode(help="Width of the video in pixels.").tag(sync=True) + height = CUnicode(help="Height of the video in pixels.").tag(sync=True) @classmethod def from_file(cls, filename, **kwargs): @@ -131,6 +135,20 @@ def from_download(cls, url, **kwargs): image = Image(value=urlopen(url).read(), format=format) return cls(image=image, **kwargs) + def __init__(self, *args, **kwargs): + super(ImageStream, self).__init__(*args, **kwargs) + self._width_link = link((self, "width"), (self.image, "width")) + self._height_link = link((self, "height"), (self.image, "height")) + + @observe('image') + def _update_links(self, change): + if (not hasattr(self, "_width_link")): + return + self._width_link.unlink() + self._width_link = link((self, "width"), (change.new, "width")) + self._height_link.unlink() + self._height_link = link((self, "height"), (change.new, "height")) + @register class VideoStream(MediaStream): @@ -143,6 +161,8 @@ class VideoStream(MediaStream): help="An ipywidgets.Video instance that will be the source of the media stream." ).tag(sync=True, **widget_serialization) playing = Bool(True, help='Plays the videostream or pauses it.').tag(sync=True) + width = CUnicode(help="Width of the video in pixels.").tag(sync=True) + height = CUnicode(help="Height of the video in pixels.").tag(sync=True) @classmethod def from_file(cls, filename, **kwargs): @@ -193,6 +213,20 @@ def from_download(cls, url, **kwargs): video = Video(value=urlopen(url).read(), format=format, autoplay=False, controls=False) return cls(video=video, **kwargs) + def __init__(self, *args, **kwargs): + super(VideoStream, self).__init__(*args, **kwargs) + self._width_link = link((self, "width"), (self.video, "width")) + self._height_link = link((self, "height"), (self.video, "height")) + + @observe('video') + def _update_links(self, change): + if (not hasattr(self, "_width_link")): + return + self._width_link.unlink() + self._width_link = link((self, "width"), (change.new, "width")) + self._height_link.unlink() + self._height_link = link((self, "height"), (change.new, "height")) + @register class AudioStream(MediaStream): diff --git a/js/src/webrtc.js b/js/src/webrtc.js index 5fc404a..3a2c80b 100644 --- a/js/src/webrtc.js +++ b/js/src/webrtc.js @@ -57,6 +57,43 @@ export class MediaStreamView extends widgets.DOMWidgetView { text.innerHTML = 'Error creating view for mediastream: ' + error.message; this.el.appendChild(text); }); + + this.model.on("change:width", this._update_width, this); + this.model.on("change:height", this._update_height, this); + } + + _update_width() { + let width = this.model.get('width'); + if (width !== undefined && width.length > 0) { + this.video.setAttribute('width', width); + } else { + this.video.removeAttribute('width'); + } + + // When resized, the source video restarts, and we need to call capture + // again + this._capture(); + } + + _update_height() { + let height = this.model.get('height'); + if (height !== undefined && height.length > 0) { + this.video.setAttribute('height', height); + } else { + this.video.removeAttribute('height'); + } + + // When resized, the source video restarts, and we need to call capture + // again + this._capture(); + } + + _capture() { + this.capturePromise = this.model.captureStream(); + this.capturePromise.then((stream) => { + this.video.srcObject = stream; + this.video.play(); + }); } remove() { @@ -79,6 +116,8 @@ export class ImageStreamModel extends MediaStreamModel { return {...super.defaults(), _model_name: 'ImageStreamModel', image: null, + width: null, + height: null, }; } @@ -182,6 +221,8 @@ export class VideoStreamModel extends StreamModel { return { ...super.defaults(), _model_name: 'VideoStreamModel', video: null, + width: null, + height: null, }; }