Use bokeh slider to control the frame in the video
What I have tried (minimum reproducible example):
from bokeh.io import output_file, show
from bokeh.layouts import column
from bokeh.models import Div, Slider, CustomJS
# Define the HTML code for the video tag
video_html = """
<video id="video_player" controls preload="metadata">
<source src="video.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
"""
# Create a Bokeh Div element to embed the video HTML
video_div = Div(text=video_html)
# Create a slider to control video playback
slider = Slider(start=0, end=100, value=0, step=1, title="Video Time (%)")
# Define a JavaScript callback to sync slider with video playback
callback = CustomJS(args=dict(slider=slider), code="""
var video = document.getElementById('video_player');
video.currentTime = slider.value * video.duration / 100;
""")
# Attach the JavaScript callback to the slider's 'value' property
slider.js_on_change('value', callback)
# Create a Bokeh layout with the video Div and slider
layout = column(video_div, slider)
# Show the Bokeh plot
output_file("video_player.html")
show(layout)
Error I see in the javascript console is the following:
TypeError: null is not an object (evaluating 'video.duration')
It seems that I cannot get the video object inside the callback.
The problem is that Bokeh puts the content of your div (and actually of the column) inside a shadow-root, which makes its content inaccessible from the rest of the document.
Here is the structure of your document:
As a consequence, document.getElementById('video_player') doesn’t find the video and returns null, leading to the error message you observed.
This problem was also discussed here, unfortunately without solution:
Since Bokeh was not designed to display videos, there is no built-in video class allowing to access a video or its properties.
A solution could be to create a dashboard with another tool than Bokeh (e.g. Flask), embedding the video outside a shadow-root, then you would be able to access the video and its properties.
Or using a html template for a Bokeh application, see:
In this case, create a directory video-app containing the following:
from bokeh.io import curdoc
from bokeh.models import Slider, CustomJS
# Create a slider to control video playback
slider = Slider(start=0, end=100, value=0, step=1, title="Video Time (%)")
# Define a JavaScript callback to sync slider with video playback
callback = CustomJS(args=dict(slider=slider), code="""
const video = document.getElementById("video_player")
let new_time = slider.value * video.duration / 100
video.currentTime = new_time
console.log("Video currentTime set to " + new_time.toFixed(2) + " s")
""")
# Attach the JavaScript callback to the slider's 'value' property
slider.js_on_change('value', callback)
curdoc().add_root(slider)
This inserts the video in the html file in an open way (so not in a shadow-root), making it accessible with document.getElementById.
Then run the app with bokeh serve --show video-app.
This works for me.
A drawback of this method is that a Bokeh server runs in the background although it is actually not needed since everything runs as Javascript.
Stopping the server, the app still works but there are error messages in the console telling that no communication with Bokeh is possible.
I can also propose following “dirty” method:
Create a Python file containing the following:
from bokeh.io import output_file, save
from bokeh.models import Slider, CustomJS
# Create a slider to control video playback
slider = Slider(start=0, end=100, value=0, step=1, title="Video Time (%)")
# Define a JavaScript callback to sync slider with video playback
callback = CustomJS(args=dict(slider=slider), code="""
var video = document.getElementById('video_player');
video.currentTime = slider.value * video.duration / 100;
""")
# Attach the JavaScript callback to the slider's 'value' property
slider.js_on_change('value', callback)
output_file("video_player.html")
save(slider)
In the created html-file, insert following lines just after <body>: