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:
video-app
|
+ - main.py
+ - templates
+ - index.html
+ - static
+ - video.mp4
In main.py
, write 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)
In index.html
, write the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
{{ bokeh_css }}
{{ bokeh_js }}
</head>
<body>
<video id="video_player" controls preload="metadata">
<source src="Video-app/static/video.mp4" type="video/mp4">
</video>
{{ plot_div|indent(8) }}
{{ plot_script|indent(8) }}
</body>
</html>
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>
:
<video id="video_player" controls preload="metadata">
<source src="Video-app/static/video.mp4" type="video/mp4">
</video>
Then open the html file, it works as standalone.