Skip to content

Commit 77ba91c

Browse files
committed
support TextTrack cuechange events
1 parent 2387092 commit 77ba91c

File tree

3 files changed

+100
-20
lines changed

3 files changed

+100
-20
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ web-sys = { version = "0.3.77", features = [
1717
"Window",
1818
"HtmlVideoElement",
1919
"HtmlMediaElement",
20+
"TextTrack",
21+
"TextTrackCueList",
22+
"TextTrackKind",
23+
"VttCue",
2024
] }
2125

2226
[package]

src/event.rs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use bevy::prelude::*;
22
use enumset::EnumSetType;
33

4-
use crate::WebVideo;
4+
use crate::{VideoElement, WebVideo};
55

66
pub(crate) struct VideoEventMessage {
77
pub asset_id: AssetId<Image>,
@@ -13,6 +13,7 @@ pub enum VideoEvents {
1313
Abort,
1414
CanPlay,
1515
CanPlayThrough,
16+
CueChange,
1617
DurationChanged,
1718
Emptied,
1819
Ended,
@@ -52,6 +53,15 @@ pub struct CanPlay;
5253
#[derive(Copy, Clone, Debug)]
5354
pub struct CanPlayThrough;
5455
#[derive(Copy, Clone, Debug)]
56+
pub struct CueChange {
57+
pub cue: Option<Cue>,
58+
}
59+
#[derive(Copy, Clone, Debug)]
60+
pub struct Cue {
61+
pub start_time: f64,
62+
pub end_time: f64,
63+
}
64+
#[derive(Copy, Clone, Debug)]
5565
pub struct DurationChanged;
5666
#[derive(Copy, Clone, Debug)]
5767
pub struct Emptied;
@@ -106,6 +116,7 @@ impl From<VideoEvents> for &'static str {
106116
VideoEvents::Abort => "abort",
107117
VideoEvents::CanPlay => "canplay",
108118
VideoEvents::CanPlayThrough => "canplaythrough",
119+
VideoEvents::CueChange => "cuechange",
109120
VideoEvents::DurationChanged => "durationchanged",
110121
VideoEvents::Emptied => "emptied",
111122
VideoEvents::Ended => "ended",
@@ -131,17 +142,56 @@ impl From<VideoEvents> for &'static str {
131142
}
132143
}
133144

145+
struct TextTrackCueListIter {
146+
cues: web_sys::TextTrackCueList,
147+
index: u32,
148+
}
149+
impl TextTrackCueListIter {
150+
fn new(cues: web_sys::TextTrackCueList) -> Self {
151+
Self { cues, index: 0 }
152+
}
153+
}
154+
impl Iterator for TextTrackCueListIter {
155+
type Item = web_sys::VttCue;
156+
157+
fn next(&mut self) -> Option<Self::Item> {
158+
let index = self.index;
159+
self.index += 1;
160+
self.cues.get(index)
161+
}
162+
}
163+
134164
pub(crate) fn dispatch_events(
135165
event_type: VideoEvents,
136166
asset_id: AssetId<Image>,
137-
video: &web_sys::HtmlVideoElement,
167+
video: &VideoElement,
138168
commands: &mut Commands,
139169
videos: Query<(Entity, &WebVideo)>,
140170
) {
141171
match event_type {
142172
VideoEvents::Abort => trigger_event(asset_id, Abort, commands, videos),
143173
VideoEvents::CanPlay => trigger_event(asset_id, CanPlay, commands, videos),
144174
VideoEvents::CanPlayThrough => trigger_event(asset_id, CanPlayThrough, commands, videos),
175+
VideoEvents::CueChange => {
176+
if let Some(ref track) = video.text_track {
177+
match track.active_cues() {
178+
Some(cues) => TextTrackCueListIter::new(cues.clone()).for_each(|cue| {
179+
trigger_event(
180+
asset_id,
181+
CueChange {
182+
cue: Some(Cue {
183+
start_time: cue.start_time(),
184+
end_time: cue.end_time(),
185+
}),
186+
},
187+
commands,
188+
videos,
189+
)
190+
}),
191+
None => trigger_event(asset_id, CueChange { cue: None }, commands, videos),
192+
}
193+
}
194+
}
145195
VideoEvents::DurationChanged => trigger_event(asset_id, DurationChanged, commands, videos),
146196
VideoEvents::Emptied => trigger_event(asset_id, Emptied, commands, videos),
147197
VideoEvents::Ended => trigger_event(asset_id, Ended, commands, videos),
@@ -150,8 +200,8 @@ pub(crate) fn dispatch_events(
150200
VideoEvents::LoadedMetadata => trigger_event(
151201
asset_id,
152202
LoadedMetadata {
153-
width: video.video_width(),
154-
height: video.video_height(),
203+
width: video.element.video_width(),
204+
height: video.element.video_height(),
155205
},
156206
commands,
157207
videos,
@@ -165,8 +215,8 @@ pub(crate) fn dispatch_events(
165215
VideoEvents::Resize => trigger_event(
166216
asset_id,
167217
Resize {
168-
width: video.video_width(),
169-
height: video.video_height(),
218+
width: video.element.video_width(),
219+
height: video.element.video_height(),
170220
},
171221
commands,
172222
videos,
@@ -201,5 +251,6 @@ fn trigger_event<E>(
201251
}
202252
})
203253
.for_each(|entity| commands.trigger_targets(video_event, entity));
254+
204255
commands.trigger(video_event);
205256
}

src/lib.rs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ thread_local! {
4949
struct VideoElement {
5050
element: web_sys::HtmlVideoElement,
5151
loaded: bool,
52+
text_track: Option<web_sys::TextTrack>,
5253
enabled_events: EnumSet<VideoEvents>,
5354
}
5455

@@ -92,6 +93,7 @@ impl WebVideoRegistry {
9293
VideoElement {
9394
element: html_video_element.clone(),
9495
loaded: false,
96+
text_track: None,
9597
enabled_events: EnumSet::empty(),
9698
},
9799
)
@@ -118,19 +120,48 @@ impl WebVideoRegistry {
118120
warn!("Failed to handle video {event_type:?}: {err:?}");
119121
}
120122
});
121-
video_element
122-
.element
123-
.add_event_listener_with_callback(
124-
event_type.into(),
125-
callback.as_ref().unchecked_ref(),
126-
)
127-
.map_err(WebVideoError::from)?;
123+
if event_type == VideoEvents::CueChange {
124+
let track = video_element.text_track.get_or_insert_with(|| {
125+
video_element
126+
.element
127+
.add_text_track(web_sys::TextTrackKind::Metadata)
128+
});
129+
track
130+
.add_event_listener_with_callback(
131+
event_type.into(),
132+
callback.as_ref().unchecked_ref(),
133+
)
134+
.map_err(WebVideoError::from)?;
135+
} else {
136+
video_element
137+
.element
138+
.add_event_listener_with_callback(
139+
event_type.into(),
140+
callback.as_ref().unchecked_ref(),
141+
)
142+
.map_err(WebVideoError::from)?;
143+
}
128144
callback.forget();
129145
video_element.enabled_events.insert(event_type);
130146
}
131147
Ok(())
132148
})
133149
}
150+
151+
pub fn add_cue(&self, cue: event::Cue, asset_id: AssetId<Image>) -> Result<()> {
152+
self.enable_observer(VideoEvents::CueChange, asset_id)?;
153+
VIDEO_ELEMENTS.with_borrow(|ve| {
154+
if let Some(video_element) = ve.get(&asset_id)
155+
&& let Some(ref text_track) = video_element.text_track
156+
{
157+
text_track.add_cue(
158+
&web_sys::VttCue::new(cue.start_time, cue.end_time, "")
159+
.map_err(WebVideoError::from)?,
160+
);
161+
}
162+
Ok(())
163+
})
164+
}
134165
}
135166

136167
impl WebVideo {
@@ -187,13 +218,7 @@ fn trigger_video_events(
187218
{
188219
VIDEO_ELEMENTS.with_borrow(|ve| {
189220
if let Some(video_element) = ve.get(&asset_id) {
190-
event::dispatch_events(
191-
event_type,
192-
asset_id,
193-
&video_element.element,
194-
&mut commands,
195-
videos,
196-
);
221+
event::dispatch_events(event_type, asset_id, video_element, &mut commands, videos);
197222
}
198223
});
199224
}

0 commit comments

Comments
 (0)