Skip to content

Commit

Permalink
Added Video model and widget
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Oct 28, 2019
1 parent d451a4b commit 4a812fc
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 18 deletions.
132 changes: 132 additions & 0 deletions panel/models/video.ts
@@ -0,0 +1,132 @@
import * as p from "@bokehjs/core/properties"
import {Widget, WidgetView} from "@bokehjs/models/widgets/widget"

export class VideoView extends WidgetView {
model: Video
protected videoEl: HTMLVideoElement
protected dialogEl: HTMLElement
private _blocked: boolean
private _time: any

initialize(): void {
super.initialize()
this._blocked = false
this._time = Date.now()
}

connect_signals(): void {
super.connect_signals()
this.connect(this.model.change, () => this.render())
this.connect(this.model.properties.loop.change, () => this.set_loop())
this.connect(this.model.properties.paused.change, () => this.set_paused())
this.connect(this.model.properties.time.change, () => this.set_time())
this.connect(this.model.properties.value.change, () => this.set_value())
this.connect(this.model.properties.volume.change, () => this.set_volume())
}

render(): void {
if (this.videoEl) {
return
}
this.videoEl = document.createElement('video')
this.videoEl.controls = true
this.videoEl.src = this.model.value
this.videoEl.currentTime = this.model.time
this.videoEl.loop = this.model.loop
if (this.model.volume != null)
this.videoEl.volume = this.model.volume/100
else
this.model.volume = this.videoEl.volume*100
this.videoEl.onpause = () => this.model.paused = true
this.videoEl.onplay = () => this.model.paused = false
this.videoEl.ontimeupdate = () => this.update_time(this)
this.videoEl.onvolumechange = () => this.update_volume(this)
this.el.appendChild(this.videoEl)
if (!this.model.paused)
this.videoEl.play()
}

update_time(view: VideoView): void {
if ((Date.now() - view._time) < view.model.throttle) {
return
}
view._blocked = true
view.model.time = view.videoEl.currentTime
view._time = Date.now()
}

update_volume(view: VideoView): void {
view._blocked = true
view.model.volume = view.videoEl.volume*100
}

set_loop(): void {
this.videoEl.loop = this.model.loop
}

set_paused(): void {
if (!this.videoEl.paused && this.model.paused)
this.videoEl.pause()
if (this.videoEl.paused && !this.model.paused)
this.videoEl.play()
}

set_volume(): void {
if (this._blocked)
this._blocked = false
return
if (this.model.volume != null)
this.videoEl.volume = (this.model.volume as number)/100
}

set_time(): void {
if (this._blocked)
this._blocked = false
return
this.videoEl.currentTime = this.model.time
}

set_value(): void {
this.videoEl.src = this.model.value
}
}

export namespace Video {
export type Attrs = p.AttrsOf<Props>
export type Props = Widget.Props & {
loop: p.Property<boolean>
paused: p.Property<boolean>
time: p.Property<number>
throttle: p.Property<number>
value: p.Property<any>
volume: p.Property<number | null>
}
}

export interface Video extends Video.Attrs {}

export abstract class Video extends Widget {
properties: Video.Props

constructor(attrs?: Partial<Video.Attrs>) {
super(attrs)
}

static __module__ = "panel.models.widgets"

static initClass(): void {
this.prototype.type = "Video"
this.prototype.default_view = VideoView

this.define<Video.Props>({
loop: [ p.Boolean, false ],
paused: [ p.Boolean, true ],
time: [ p.Number, 0 ],
throttle: [ p.Number, 250 ],
value: [ p.Any, '' ],
volume: [ p.Number, null ],
})
}
}

Video.initClass()
20 changes: 20 additions & 0 deletions panel/models/widgets.py
Expand Up @@ -50,6 +50,26 @@ class Audio(Widget):
volume = Int(0, help="""The volume of the audio player.""")


class Video(Widget):

__implementation__ = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'video.ts')

loop = Bool(False, help="""Whether the video should loop""")

paused = Bool(False, help="""Whether the video is paused""")

time = Float(0, help="""
The current time stamp of the video playback""")

throttle = Int(250, help="""
The frequency at which the time value is updated in milliseconds.""")

value = Any(help="Encoded file data")

volume = Int(0, help="""The volume of the video player.""")



class VideoStream(Widget):

format = Enum('png', 'jpeg', default='png')
Expand Down
10 changes: 5 additions & 5 deletions panel/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 36 additions & 13 deletions panel/widgets/misc.py
Expand Up @@ -15,14 +15,15 @@
from ..io.notebook import push
from ..io.state import state
from ..models import (Audio as _BkAudio,
Video as _BkVideo,
VideoStream as _BkVideoStream)
from .base import Widget


class Audio(Widget):
class MediaBase(Widget):

loop = param.Boolean(default=False, doc="""
Whether the audio should loop""")
Whether the meida should loop""")

time = param.Number(default=0, doc="""
The current timestamp""")
Expand All @@ -31,21 +32,20 @@ class Audio(Widget):
How frequently to sample the current playback time in milliseconds""")

paused = param.Boolean(default=True, doc="""
Whether the audio is currently paused""")

sample_rate = param.Integer(default=44100, doc="""
The sample_rate of the audio when given a NumPy array.""")

value = param.ClassSelector(default='', class_=(string_types + (np.ndarray,)), doc="""
The audio file either local or remote.""")
Whether the media is currently paused""")

value = param.String(default='', doc="""
The media file either local or remote.""")

volume = param.Number(default=None, bounds=(0, 100), doc="""
The volume of the audio player.""")

_widget_type = _BkAudio
The volume of the media player.""")

_rename = {'name': None, 'sample_rate': None}

_default_mime = None

_media_type = None

def _from_numpy(self, data):
from scipy.io import wavfile
buffer = BytesIO()
Expand All @@ -69,7 +69,7 @@ def _process_param_change(self, msg):
elif value.lower().startswith('http'):
return msg
elif not value:
data, fmt = b'', 'wav'
data, fmt = b'', self._default_mime
else:
raise ValueError('Value should be either path to a sound file or numpy array')
template = 'data:audio/{mime};base64,{data}'
Expand All @@ -78,6 +78,29 @@ def _process_param_change(self, msg):
return msg


class Audio(Widget):

sample_rate = param.Integer(default=44100, doc="""
The sample_rate of the audio when given a NumPy array.""")

value = param.ClassSelector(default='', class_=(string_types + (np.ndarray,)), doc="""
The audio file either local or remote.""")

_media_type = 'audio'

_default_mime = 'wav'

_widget_type = _BkAudio


class Video(Widget):

_media_type = 'video'

_default_mime = 'mp4'

_widget_type = _BkVideo


class VideoStream(Widget):

Expand Down

0 comments on commit 4a812fc

Please sign in to comment.