Mercurial > libervia-web
comparison libervia/pages/_browser/alt_media_player.py @ 1337:f0648005cd11
browser: alternative media player:
the new `alt_media_player` module implements an `ogv.js` based media player which is able
to play WebM (VP8/VP9) or Ogg (Theora, Vorbis) formats (+ other codecs not integrated
yet) on browser that don't do it natively.
The module imports `ogv.js` only when it's necessary (incompatible browser + videos
needing it).
This for now used in blog, and will be used in other places when it's suitable.
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 15 Aug 2020 16:45:23 +0200 |
parents | |
children | 472267dcd4d8 |
comparison
equal
deleted
inserted
replaced
1336:71f24ad99a2c | 1337:f0648005cd11 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 """This module implement an ogv.js based alternative media player | |
4 | |
5 This is useful to play libre video/audio formats on browser that don't do it natively. | |
6 """ | |
7 | |
8 from browser import document, timer | |
9 | |
10 | |
11 class VideoPlayer: | |
12 TIMER_MODES = ("timer", "remaining") | |
13 imports_done = False | |
14 | |
15 def __init__(self, ori_video_elt, sources): | |
16 self.video_player_elt = video_player_elt = video_player_tpl.get_elt() | |
17 self.player = player = self.ogv.OGVPlayer.new() # {"debug": True}) | |
18 ori_video_elt.parentNode.replaceChild(video_player_elt, ori_video_elt) | |
19 overlay_play_elt = self.video_player_elt.select_one(".video_overlay_play") | |
20 overlay_play_elt.bind("click", self.on_overlay_play_elt_click) | |
21 self.progress_elt = video_player_elt.select_one("progress") | |
22 self.progress_elt.bind("click", self.on_progress_click) | |
23 self.timer_elt = video_player_elt.select_one(".timer") | |
24 self.timer_mode = "timer" | |
25 self.controls_elt = video_player_elt.select_one(".video_controls") | |
26 player_wrapper_elt = video_player_elt.select_one(".video_elt") | |
27 player.src = sources[0] | |
28 player_wrapper_elt <= player | |
29 self.hide_controls_timer = None | |
30 | |
31 # a click on the video itself is like click on play icon | |
32 player_wrapper_elt.bind("click", self.on_play_click) | |
33 | |
34 # buttons | |
35 for handler in ("play", "change_timer_mode", "change_volume", "fullscreen"): | |
36 for elt in video_player_elt.select(f".click_to_{handler}"): | |
37 elt.bind("click", getattr(self, f"on_{handler}_click")) | |
38 # events | |
39 # FIXME: progress is not implemented in OGV.js, update when available | |
40 for event in ("play", "pause", "timeupdate", "ended", "volumechange"): | |
41 player.bind(event, getattr(self, f"on_{event}")) | |
42 | |
43 def on_overlay_play_elt_click(self, evt): | |
44 evt.stopPropagation() | |
45 evt.target.remove() | |
46 self.player.play() | |
47 | |
48 def on_play_click(self, evt): | |
49 if self.player.paused: | |
50 print("playing") | |
51 self.player.play() | |
52 else: | |
53 self.player.pause() | |
54 print("paused") | |
55 | |
56 def on_change_timer_mode_click(self, evt): | |
57 self.timer_mode = self.TIMER_MODES[ | |
58 (self.TIMER_MODES.index(self.timer_mode) + 1) % len(self.TIMER_MODES) | |
59 ] | |
60 | |
61 def on_change_volume_click(self, evt): | |
62 self.player.muted = not self.player.muted | |
63 | |
64 def on_fullscreen_click(self, evt): | |
65 try: | |
66 fullscreen_elt = document.fullscreenElement | |
67 request_fullscreen = self.video_player_elt.requestFullscreen | |
68 except AttributeError: | |
69 print("fullscreen is not available on this browser") | |
70 else: | |
71 if fullscreen_elt == None: | |
72 print("requesting fullscreen") | |
73 request_fullscreen() | |
74 else: | |
75 print(f"leaving fullscreen: {fullscreen_elt}") | |
76 try: | |
77 document.exitFullscreen() | |
78 except AttributeError: | |
79 print("exitFullscreen not available on this browser") | |
80 | |
81 def on_progress_click(self, evt): | |
82 position = evt.offsetX / evt.target.width | |
83 new_time = self.player.duration * position | |
84 self.player.currentTime = new_time | |
85 | |
86 def on_play(self, evt): | |
87 self.video_player_elt.classList.add("playing") | |
88 self.show_controls() | |
89 self.video_player_elt.bind("mousemove", self.on_mouse_move) | |
90 | |
91 def on_pause(self, evt): | |
92 self.video_player_elt.classList.remove("playing") | |
93 self.show_controls() | |
94 self.video_player_elt.unbind("mousemove") | |
95 | |
96 def on_timeupdate(self, evt): | |
97 self.update_progress() | |
98 | |
99 def on_ended(self, evt): | |
100 self.update_progress() | |
101 | |
102 def on_volumechange(self, evt): | |
103 if self.player.muted: | |
104 self.video_player_elt.classList.add("muted") | |
105 else: | |
106 self.video_player_elt.classList.remove("muted") | |
107 | |
108 def on_mouse_move(self, evt): | |
109 self.show_controls() | |
110 | |
111 def update_progress(self): | |
112 duration = self.player.duration | |
113 current_time = duration if self.player.ended else self.player.currentTime | |
114 self.progress_elt.max = duration | |
115 self.progress_elt.value = current_time | |
116 self.progress_elt.text = f"{current_time/duration*100:.02f}" | |
117 current_time, duration = int(current_time), int(duration) | |
118 if self.timer_mode == "timer": | |
119 cur_min, cur_sec = divmod(current_time, 60) | |
120 tot_min, tot_sec = divmod(duration, 60) | |
121 self.timer_elt.text = f"{cur_min}:{cur_sec:02d}/{tot_min}:{tot_sec:02d}" | |
122 elif self.timer_mode == "remaining": | |
123 rem_min, rem_sec = divmod(duration - current_time, 60) | |
124 self.timer_elt.text = f"{rem_min}:{rem_sec:02d}" | |
125 else: | |
126 print(f"ERROR: unknown timer mode: {self.timer_mode}") | |
127 | |
128 def hide_controls(self): | |
129 self.controls_elt.classList.add("hidden") | |
130 self.video_player_elt.style.cursor = "none" | |
131 if self.hide_controls_timer is not None: | |
132 timer.clear_timeout(self.hide_controls_timer) | |
133 self.hide_controls_timer = None | |
134 | |
135 def show_controls(self): | |
136 self.controls_elt.classList.remove("hidden") | |
137 self.video_player_elt.style.cursor = "" | |
138 if self.hide_controls_timer is not None: | |
139 timer.clear_timeout(self.hide_controls_timer) | |
140 if self.player.paused: | |
141 self.hide_controls_timer = None | |
142 else: | |
143 self.hide_controls_timer = timer.set_timeout(self.hide_controls, 3000) | |
144 | |
145 @classmethod | |
146 def do_imports(cls): | |
147 # we do imports (notably for ogv.js) if they are necessary | |
148 if cls.imports_done: | |
149 return | |
150 from js_modules import ogv | |
151 cls.ogv = ogv | |
152 if not ogv.OGVCompat.supported('OGVPlayer'): | |
153 print("Can't use OGVPlayer with this browser") | |
154 raise NotImplementedError | |
155 import template | |
156 global video_player_tpl | |
157 video_player_tpl = template.Template("components/video_player.html") | |
158 cls.imports_done = True | |
159 | |
160 @classmethod | |
161 def install(cls, cant_play): | |
162 ext_list = set() | |
163 for data in cant_play.values(): | |
164 ext_list.update(data['ext']) | |
165 for ori_video_elt in document.body.select('video'): | |
166 sources = [] | |
167 src = (ori_video_elt.src or '').strip() | |
168 if src: | |
169 sources.append(src) | |
170 | |
171 for source_elt in ori_video_elt.select('source'): | |
172 src = (source_elt.src or '').strip() | |
173 if src: | |
174 sources.append(src) | |
175 | |
176 # FIXME: we only use first found source | |
177 try: | |
178 source = sources[0] | |
179 except IndexError: | |
180 print(f"Can't find any source for following elt:\n{ori_video_elt.html}") | |
181 continue | |
182 | |
183 try: | |
184 ext = f".{source.rsplit('.', 1)[1]}" | |
185 except IndexError: | |
186 print( | |
187 f"No extension found for source of following elt:\n{ori_video_elt.html}") | |
188 continue | |
189 if ext and ext in ext_list: | |
190 cls.do_imports() | |
191 print(f"alternative player will be used for {source!r}") | |
192 cls(ori_video_elt, sources) | |
193 | |
194 | |
195 def install_if_needed(): | |
196 CONTENT_TYPES = { | |
197 "ogg_theora": {"type": 'video/ogg; codecs="theora"', "ext": [".ogg"]}, | |
198 "webm_vp8": {"type": 'video/webm; codecs="vp8, vorbis"', "ext": [".webm"]}, | |
199 "webm_vp9": {"type": 'video/webm; codecs="vp9"', "ext": [".webm"]}, | |
200 # FIXME: handle audio | |
201 # "ogg_vorbis": {"type": 'audio/ogg; codecs="vorbis"', "ext": ".ogg"}, | |
202 } | |
203 test_video_elt = document.createElement("video") | |
204 cant_play = {k:d for k,d in CONTENT_TYPES.items() if test_video_elt.canPlayType(d['type']) != "probably"} | |
205 | |
206 if cant_play: | |
207 cant_play_list = '\n'.join(f"- {k} ({d['type']})" for k, d in cant_play.items()) | |
208 print( | |
209 "This browser is incompatible with following content types, using alternative:\n" | |
210 f"{cant_play_list}" | |
211 ) | |
212 try: | |
213 VideoPlayer.install(cant_play) | |
214 except NotImplementedError: | |
215 pass | |
216 else: | |
217 print("This browser can play natively all requested open video/audio formats") |