Skip to main content

atmos/os_lib/js/builtins/
mse.rs

1//! W3C Media Source Extensions (MSE) の JavaScript バインディング
2//!
3//! `MediaSource`、`SourceBuffer`、`SourceBufferList`、`TimeRanges` のコンストラクタおよび
4//! ネイティブメソッドを提供する。
5
6use super::*;
7use crate::os_lib::mse::{MediaSourceCore, SourceBufferCore, TimeRanges};
8
9/// SourceBuffer の実体(fMP4 の解析結果)。JS オブジェクトの `_sb_id` で引く。
10/// 【2026-10-17】従来の JS 側はバイト列を読まずに仮の buffered を作るだけだった。
11/// 再生側(<video>)がここからサンプルを取り出す。
12pub(crate) static SOURCE_BUFFERS: spin::Mutex<Vec<SourceBufferCore>> = spin::Mutex::new(Vec::new());
13static NEXT_SB_ID: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(1);
14static NEXT_MS_ID: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(1);
15
16/// `URL.createObjectURL(mediaSource)` の URL と MediaSource の番号、endOfStream 済みか。
17/// 再生スレッド(JS のオブジェクトに触れない)が `<video src>` から SourceBuffer を引くのに使う。
18pub(crate) static MEDIA_SOURCE_URLS: spin::Mutex<Vec<(String, usize, bool)>> = spin::Mutex::new(Vec::new());
19
20fn ms_id_of(v: &Value) -> usize {
21    match v {
22        Value::Object(o) => o.borrow().props.get("_ms_id").map_or(0, |x| x.to_number() as usize),
23        _ => 0,
24    }
25}
26
27/// `URL.createObjectURL(ms)` から呼ぶ。
28pub(crate) fn register_media_source_url(url: &str, ms: &Value) {
29    let id = ms_id_of(ms);
30    if id != 0 {
31        MEDIA_SOURCE_URLS.lock().push((String::from(url), id, false));
32    }
33}
34
35/// MSE の `<video>.readyState`: 初期化セグメントのみ = 1(HAVE_METADATA)、サンプルあり = 4。
36pub(crate) fn media_source_ready_state(url: &str) -> Option<u8> {
37    let (id, _) = media_source_for_url(url)?;
38    let list = SOURCE_BUFFERS.lock();
39    let mine: Vec<&SourceBufferCore> = list.iter().filter(|b| b.owner == id).collect();
40    if mine.iter().any(|b| !b.samples.is_empty()) {
41        Some(4)
42    } else if mine.iter().any(|b| !b.demux.tracks.is_empty()) {
43        Some(1)
44    } else {
45        Some(0)
46    }
47}
48
49/// MSE の buffered の終わり(秒。全 SourceBuffer の最大)。
50pub(crate) fn media_source_buffered_end(url: &str) -> Option<f64> {
51    let (id, _) = media_source_for_url(url)?;
52    let list = SOURCE_BUFFERS.lock();
53    list.iter()
54        .filter(|b| b.owner == id)
55        .filter_map(|b| b.buffered.ranges.last().map(|r| r.end))
56        .reduce(f64::max)
57}
58
59/// MSE の `<video>.buffered`: サンプルを持つ全 SourceBuffer の buffered の共通部分。
60pub(crate) fn media_element_buffered(url: &str) -> Option<Value> {
61    let (id, _) = media_source_for_url(url)?;
62    let list = SOURCE_BUFFERS.lock();
63    let tr = list
64        .iter()
65        .filter(|b| b.owner == id && !b.buffered.is_empty())
66        .map(|b| b.buffered.clone())
67        .reduce(|acc, b| acc.intersect(&b))
68        .unwrap_or_default();
69    Some(make_time_ranges_object(&tr))
70}
71
72/// MSE の `<video>.seekable`: `[0, duration]`(長さが分からなければ空)。
73pub(crate) fn media_element_seekable(duration: Option<f64>) -> Value {
74    let mut tr = TimeRanges::new();
75    if let Some(d) = duration {
76        tr.add(0.0, d);
77    }
78    make_time_ranges_object(&tr)
79}
80
81/// MSE の `<video>.videoWidth` / `.videoHeight`: SourceBuffer の初期化セグメントから得られた寸法。
82pub(crate) fn media_source_video_dimensions(url: &str) -> Option<(u32, u32)> {
83    let (id, _) = media_source_for_url(url)?;
84    let list = SOURCE_BUFFERS.lock();
85    list.iter()
86        .filter(|b| b.owner == id)
87        .find_map(SourceBufferCore::video_dimensions)
88}
89
90/// blob URL → (MediaSource の番号, endOfStream 済みか)。
91pub(crate) fn media_source_for_url(url: &str) -> Option<(usize, bool)> {
92    MEDIA_SOURCE_URLS.lock().iter().find(|(u, _, _)| u == url).map(|(_, id, ended)| (*id, *ended))
93}
94
95/// `MediaSource` コンストラクタ (`new MediaSource()`)
96pub(crate) fn media_source_ctor(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
97    let obj = match this {
98        Value::Object(o) => o,
99        _ => Obj::plain(),
100    };
101
102    let source_buffers = Obj::array(alloc::vec![]);
103    let active_buffers = Obj::array(alloc::vec![]);
104
105    {
106        let mut m = obj.borrow_mut();
107        m.props.insert("readyState".into(), Value::str("closed"));
108        m.props.insert("duration".into(), Value::Number(0.0));
109        m.props.insert("sourceBuffers".into(), Value::Object(source_buffers.clone()));
110        m.props.insert("activeSourceBuffers".into(), Value::Object(active_buffers));
111        m.props.insert("addSourceBuffer".into(), nv("MediaSource.addSourceBuffer", media_source_add_source_buffer));
112        m.props.insert("removeSourceBuffer".into(), nv("MediaSource.removeSourceBuffer", media_source_remove_source_buffer));
113        m.props.insert("endOfStream".into(), nv("MediaSource.endOfStream", media_source_end_of_stream));
114        m.props.insert("setLiveSeekableRange".into(), nv("MediaSource.setLiveSeekableRange", media_source_set_live_seekable_range));
115        m.props.insert("clearLiveSeekableRange".into(), nv("MediaSource.clearLiveSeekableRange", media_source_clear_live_seekable_range));
116        m.props.insert("_listeners".into(), Value::Object(Obj::plain()));
117        m.props.insert("_is_media_source".into(), Value::Bool(true));
118        m.props.insert("addEventListener".into(), nv("addEventListener", event_target_add_event_listener));
119        m.props.insert("removeEventListener".into(), nv("removeEventListener", event_target_remove_event_listener));
120        m.props.insert("dispatchEvent".into(), nv("dispatchEvent", event_target_dispatch_event));
121        m.props.insert("onsourceopen".into(), Value::Null);
122        m.props.insert("onsourceended".into(), Value::Null);
123        m.props.insert("onsourceclose".into(), Value::Null);
124        m.props.insert("_core".into(), Value::Object(Obj::plain()));
125        m.props.insert(
126            "_ms_id".into(),
127            Value::Number(NEXT_MS_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed) as f64),
128        );
129    }
130
131    Ok(Value::Object(obj))
132}
133
134/// MediaSource を open 状態へ遷移させ、sourceopen イベントをディスパッチする
135pub(crate) fn media_source_open_internal(it: &mut Interp, ms: &Value) {
136    if let Value::Object(o) = ms {
137        let is_already_open = {
138            let b = o.borrow();
139            b.props.get("readyState").map(|v| v.to_js_string()).as_deref() == Some("open")
140        };
141        if !is_already_open {
142            o.borrow_mut().props.insert("readyState".into(), Value::str("open"));
143            dispatch_mse_event(it, ms, "sourceopen");
144        }
145    }
146}
147
148/// MSE / SourceBuffer のイベントディスパッチ(on<type> と addEventListener の両方を実行)
149pub(crate) fn dispatch_mse_event(it: &mut Interp, target: &Value, event_type: &str) {
150    let ev = Obj::plain();
151    {
152        let mut e = ev.borrow_mut();
153        e.props.insert("type".into(), Value::str(event_type));
154        e.props.insert("target".into(), target.clone());
155        e.props.insert("currentTarget".into(), target.clone());
156    }
157    let ev_val = Value::Object(ev);
158
159    // 1. on<type> プロパティハンドラ
160    let handler_key = alloc::format!("on{}", event_type);
161    let handler = if let Value::Object(t) = target {
162        t.borrow().props.get(&handler_key).cloned()
163    } else {
164        None
165    };
166    // `on<type>` が null / 関数以外なら呼ばない(従来は null を呼んで例外を報告していた)。
167    if let Some(h) = handler.filter(|h| matches!(h, Value::Object(o) if o.borrow().is_callable())) {
168        let ctx = alloc::format!("{} on-handler", event_type);
169        it.call_listener(&h, target.clone(), core::slice::from_ref(&ev_val), &ctx);
170    }
171
172    // 2. addEventListener リスナー
173    let _ = event_target_dispatch_event(it, target.clone(), &[ev_val]);
174}
175
176/// `MediaSource.isTypeSupported(mimeType)` 静的メソッド
177pub(crate) fn media_source_is_type_supported(_it: &mut Interp, _this: Value, a: &[Value]) -> Result<Value, Value> {
178    let mime = arg(a, 0).to_js_string();
179    let supported = MediaSourceCore::is_type_supported(&mime);
180    Ok(Value::Bool(supported))
181}
182
183/// `ms.addSourceBuffer(mimeType)`
184pub(crate) fn media_source_add_source_buffer(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
185    let mime = arg(a, 0).to_js_string();
186    if !MediaSourceCore::is_type_supported(&mime) {
187        return Err(it.type_error(alloc::format!("Unsupported MIME type: {}", mime)));
188    }
189
190    if let Value::Object(ms_obj) = &this {
191        let mut m = ms_obj.borrow_mut();
192        // open 状態でなければ InvalidStateError
193        let ready_state = m.props.get("readyState").map(|v| v.to_js_string()).unwrap_or_else(|| "closed".into());
194        if ready_state != "open" {
195            // 自動で open する(ブラウザで URL 割り当て時等)
196            m.props.insert("readyState".into(), Value::str("open"));
197        }
198
199        let owner = m.props.get("_ms_id").map_or(0, |x| x.to_number() as usize);
200        let sb_obj = make_source_buffer_object(&mime, owner);
201
202        if let Some(Value::Object(sbl)) = m.props.get("sourceBuffers") {
203            if let ObjKind::Array(ref mut vec) = sbl.borrow_mut().kind {
204                vec.push(sb_obj.clone());
205            }
206        }
207
208        return Ok(sb_obj);
209    }
210
211    Err(it.type_error("addSourceBuffer called on non-MediaSource"))
212}
213
214/// `ms.removeSourceBuffer(sourceBuffer)`
215pub(crate) fn media_source_remove_source_buffer(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
216    let target = arg(a, 0);
217    if let Value::Object(ms_obj) = &this {
218        let m = ms_obj.borrow();
219        if let Some(Value::Object(sbl)) = m.props.get("sourceBuffers") {
220            if let ObjKind::Array(ref mut vec) = sbl.borrow_mut().kind {
221                if let Some(pos) = vec.iter().position(|v| match (v, &target) {
222                    (Value::Object(o1), Value::Object(o2)) => Rc::ptr_eq(o1, o2),
223                    _ => false,
224                }) {
225                    vec.remove(pos);
226                }
227            }
228        }
229    }
230    Ok(Value::Undefined)
231}
232
233/// `ms.endOfStream(error)`
234pub(crate) fn media_source_end_of_stream(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
235    if let Value::Object(ms_obj) = &this {
236        ms_obj.borrow_mut().props.insert("readyState".into(), Value::str("ended"));
237    }
238    let id = ms_id_of(&this);
239    for e in MEDIA_SOURCE_URLS.lock().iter_mut().filter(|e| e.1 == id) {
240        e.2 = true;
241    }
242    Ok(Value::Undefined)
243}
244
245/// `ms.setLiveSeekableRange(start, end)`
246pub(crate) fn media_source_set_live_seekable_range(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
247    let start = arg(a, 0).to_number();
248    let end = arg(a, 1).to_number();
249    if !start.is_finite() || !end.is_finite() || start < 0.0 || start > end {
250        return Err(it.type_error("Invalid live seekable range"));
251    }
252    if let Value::Object(ms_obj) = &this {
253        let mut m = ms_obj.borrow_mut();
254        let rs = m.props.get("readyState").map(|v| v.to_js_string()).unwrap_or_default();
255        if rs != "open" {
256            return Err(it.type_error("MediaSource is not open"));
257        }
258        m.props.insert("_live_seekable_start".into(), Value::Number(start));
259        m.props.insert("_live_seekable_end".into(), Value::Number(end));
260        return Ok(Value::Undefined);
261    }
262    Err(it.type_error("setLiveSeekableRange called on non-MediaSource"))
263}
264
265/// `ms.clearLiveSeekableRange()`
266pub(crate) fn media_source_clear_live_seekable_range(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
267    if let Value::Object(ms_obj) = &this {
268        let mut m = ms_obj.borrow_mut();
269        let rs = m.props.get("readyState").map(|v| v.to_js_string()).unwrap_or_default();
270        if rs != "open" {
271            return Err(it.type_error("MediaSource is not open"));
272        }
273        m.props.shift_remove("_live_seekable_start");
274        m.props.shift_remove("_live_seekable_end");
275        return Ok(Value::Undefined);
276    }
277    Err(it.type_error("clearLiveSeekableRange called on non-MediaSource"))
278}
279
280/// `SourceBuffer` JS オブジェクトを構築
281fn make_source_buffer_object(mime_type: &str, owner: usize) -> Value {
282    let id = NEXT_SB_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
283    let mut core = SourceBufferCore::new(id, mime_type);
284    core.owner = owner;
285    SOURCE_BUFFERS.lock().push(core);
286    let sb = Obj::plain();
287    {
288        let mut s = sb.borrow_mut();
289        s.props.insert("_sb_id".into(), Value::Number(id as f64));
290        s.props.insert("mode".into(), Value::str("segments"));
291        s.props.insert("timestampOffset".into(), Value::Number(0.0));
292        s.props.insert("appendWindowStart".into(), Value::Number(0.0));
293        s.props.insert("appendWindowEnd".into(), Value::Number(f64::INFINITY));
294        s.props.insert("mimeType".into(), Value::str(mime_type));
295        s.props.insert("updating".into(), Value::Bool(false));
296        s.props.insert("buffered".into(), make_time_ranges_object(&TimeRanges::new()));
297        s.props.insert("appendBuffer".into(), nv("SourceBuffer.appendBuffer", source_buffer_append_buffer));
298        s.props.insert("changeType".into(), nv("SourceBuffer.changeType", source_buffer_change_type));
299        s.props.insert("abort".into(), nv("SourceBuffer.abort", source_buffer_abort));
300        s.props.insert("remove".into(), nv("SourceBuffer.remove", source_buffer_remove));
301        s.props.insert("_listeners".into(), Value::Object(Obj::plain()));
302        s.props.insert("addEventListener".into(), nv("addEventListener", event_target_add_event_listener));
303        s.props.insert("removeEventListener".into(), nv("removeEventListener", event_target_remove_event_listener));
304        s.props.insert("dispatchEvent".into(), nv("dispatchEvent", event_target_dispatch_event));
305        s.props.insert("onupdatestart".into(), Value::Null);
306        s.props.insert("onupdate".into(), Value::Null);
307        s.props.insert("onupdateend".into(), Value::Null);
308        s.props.insert("onerror".into(), Value::Null);
309        s.props.insert("onabort".into(), Value::Null);
310        s.props.insert("_data".into(), Value::Object(Obj::array(alloc::vec![])));
311    }
312    Value::Object(sb)
313}
314
315/// `sb.appendBuffer(data)`
316pub(crate) fn source_buffer_append_buffer(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
317    let data_val = arg(a, 0);
318    if let Value::Object(sb_obj) = &this {
319        let (updating, id, mode_str, ts_offset, win_start, win_end) = {
320            let s = sb_obj.borrow();
321            let u = s.props.get("updating").map(|v| v.truthy()).unwrap_or(false);
322            let id = s.props.get("_sb_id").map_or(0, |v| v.to_number() as usize);
323            let mode = s.props.get("mode").map(|v| v.to_js_string()).unwrap_or_else(|| "segments".into());
324            let tso = s.props.get("timestampOffset").map_or(0.0, |v| v.to_number());
325            let ws = s.props.get("appendWindowStart").map_or(0.0, |v| v.to_number());
326            let we = s.props.get("appendWindowEnd").map_or(f64::INFINITY, |v| v.to_number());
327            (u, id, mode, tso, ws, we)
328        };
329        if updating {
330            return Err(it.type_error("Cannot appendBuffer while SourceBuffer is updating"));
331        }
332
333        // バイト列の取り出し(ArrayBuffer / TypedArray / DataView / 文字列)。
334        let mut raw: Vec<f64> = Vec::new();
335        super::typed_array::blob_part_bytes(&data_val, &mut raw);
336        let bytes: Vec<u8> = raw.iter().map(|&v| v as i64 as u8).collect();
337        dispatch_mse_event(it, &this, "updatestart");
338        let result = {
339            let mut list = SOURCE_BUFFERS.lock();
340            match list.iter_mut().find(|b| b.id == id) {
341                Some(core) => {
342                    let _ = core.set_mode(&mode_str);
343                    let _ = core.set_timestamp_offset(ts_offset);
344                    if win_end > core.append_window_start {
345                        let _ = core.set_append_window_end(win_end);
346                        let _ = core.set_append_window_start(win_start);
347                    } else {
348                        let _ = core.set_append_window_start(win_start);
349                        let _ = core.set_append_window_end(win_end);
350                    }
351                    core.append_buffer(&bytes)
352                        .map(|_| (core.buffered.clone(), core.timestamp_offset))
353                }
354                None => Err(crate::os_lib::mse::MseError::BufferNotFound),
355            }
356        };
357        // 取り込みはここで済ませ、`updating` を立てたまま戻る。`update`/`updateend`(失敗なら
358        // `error`/`updateend`)は次のタスクで発火する(仕様どおり非同期)。【2026-10-18】従来は
359        // その場で発火しており、`updateend` のリスナが次の `appendBuffer` を呼ぶ YouTube の
360        // player で再帰になりスタックが尽きた。
361        let ok = match result {
362            Ok((tr, new_ts_offset)) => {
363                let tr_obj = make_time_ranges_object(&tr);
364                let mut s = sb_obj.borrow_mut();
365                s.props.insert("buffered".into(), tr_obj);
366                s.props.insert("timestampOffset".into(), Value::Number(new_ts_offset));
367                true
368            }
369            Err(e) => {
370                crate::warn!("[MSE] appendBuffer failed ({} bytes): {:?}", bytes.len(), e);
371                false
372            }
373        };
374        sb_obj.borrow_mut().props.insert("updating".into(), Value::Bool(true));
375        schedule_sb_completion(it, &this, ok);
376        return Ok(Value::Undefined);
377    }
378    Err(it.type_error("appendBuffer called on non-SourceBuffer"))
379}
380
381/// SourceBuffer の更新の完了(`updating=false` と `update`/`updateend`)を次のタスクで行う。
382fn schedule_sb_completion(it: &mut Interp, sb: &Value, ok: bool) {
383    let cb = nv("SourceBuffer update completion", sb_completion_task);
384    let _ = super::timers_gen::set_timeout(it, Value::Undefined, &[cb, Value::Number(0.0), sb.clone(), Value::Bool(ok)]);
385}
386
387fn sb_completion_task(it: &mut Interp, _this: Value, a: &[Value]) -> Result<Value, Value> {
388    let sb = arg(a, 0);
389    let ok = arg(a, 1).truthy();
390    if let Value::Object(o) = &sb {
391        o.borrow_mut().props.insert("updating".into(), Value::Bool(false));
392    }
393    if ok {
394        dispatch_mse_event(it, &sb, "update");
395    } else {
396        dispatch_mse_event(it, &sb, "error");
397    }
398    dispatch_mse_event(it, &sb, "updateend");
399    fire_media_ready_events(it);
400    Ok(Value::Undefined)
401}
402
403/// MSE を src に持つ `<video>`/`<audio>` の readyState が上がったらメディアイベントを発火する
404/// (HAVE_METADATA で `loadedmetadata`・`durationchange`、HAVE_ENOUGH_DATA で `loadeddata`・
405/// `canplay`・`canplaythrough`)。【2026-10-18】従来は発火せず、これらを待ってから `play()` する
406/// YouTube の player が再生を始めなかった。
407fn fire_media_ready_events(it: &mut Interp) {
408    let targets: Vec<(usize, String, u8)> = {
409        let dom = it.dom.borrow();
410        dom.nodes
411            .iter()
412            .enumerate()
413            .filter(|(_, n)| n.tag == "video" || n.tag == "audio")
414            .filter_map(|(i, n)| {
415                let src = n.attrs.get("src")?;
416                if !src.starts_with("blob:mediasource") {
417                    return None;
418                }
419                let prev = n.attrs.get("_rs_fired").and_then(|v| v.parse::<u8>().ok()).unwrap_or(0);
420                Some((i, src.clone(), prev))
421            })
422            .collect()
423    };
424    for (idx, src, prev) in targets {
425        let rs = media_source_ready_state(&src).unwrap_or(0);
426        if rs >= 1 && prev < 1 {
427            it.dom.borrow_mut().set_attr(idx, "_rs_fired", "1");
428            let _ = it.dispatch_event_in_interp(idx, "loadedmetadata", &[]);
429            let _ = it.dispatch_event_in_interp(idx, "durationchange", &[]);
430        }
431        if rs >= 4 && prev < 4 {
432            it.dom.borrow_mut().set_attr(idx, "_rs_fired", "4");
433            let _ = it.dispatch_event_in_interp(idx, "loadeddata", &[]);
434            let _ = it.dispatch_event_in_interp(idx, "canplay", &[]);
435            let _ = it.dispatch_event_in_interp(idx, "canplaythrough", &[]);
436        }
437    }
438}
439
440/// `sb.changeType(type)`
441pub(crate) fn source_buffer_change_type(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
442    let mime = arg(a, 0).to_js_string();
443    if mime.trim().is_empty() {
444        return Err(it.type_error("changeType requires a non-empty MIME type"));
445    }
446    if let Value::Object(sb_obj) = &this {
447        let (updating, id) = {
448            let s = sb_obj.borrow();
449            let u = s.props.get("updating").map(|v| v.truthy()).unwrap_or(false);
450            let id = s.props.get("_sb_id").map_or(0, |v| v.to_number() as usize);
451            (u, id)
452        };
453        if updating {
454            return Err(it.type_error("Cannot changeType while SourceBuffer is updating"));
455        }
456        let res = {
457            let mut list = SOURCE_BUFFERS.lock();
458            match list.iter_mut().find(|b| b.id == id) {
459                Some(core) => core.change_type(&mime),
460                None => {
461                    if MediaSourceCore::is_type_supported(&mime) {
462                        Ok(())
463                    } else {
464                        Err(crate::os_lib::mse::MseError::UnsupportedType)
465                    }
466                }
467            }
468        };
469        match res {
470            Ok(()) => {
471                sb_obj.borrow_mut().props.insert("mimeType".into(), Value::str(&mime));
472                return Ok(Value::Undefined);
473            }
474            Err(_) => {
475                return Err(it.type_error(alloc::format!("Unsupported MIME type for changeType: {}", mime)));
476            }
477        }
478    }
479    Err(it.type_error("changeType called on non-SourceBuffer"))
480}
481
482pub(crate) fn source_buffer_abort(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
483    if let Value::Object(sb_obj) = &this {
484        let (was_updating, id) = {
485            let mut s = sb_obj.borrow_mut();
486            let u = s.props.get("updating").map(|v| v.truthy()).unwrap_or(false);
487            let id = s.props.get("_sb_id").map_or(0, |v| v.to_number() as usize);
488            s.props.insert("updating".into(), Value::Bool(false));
489            (u, id)
490        };
491        if let Some(core) = SOURCE_BUFFERS.lock().iter_mut().find(|b| b.id == id) {
492            core.abort();
493        }
494        if was_updating {
495            dispatch_mse_event(it, &this, "abort");
496            dispatch_mse_event(it, &this, "updateend");
497        }
498    }
499    Ok(Value::Undefined)
500}
501
502/// `sb.remove(start, end)`。区間内のサンプルを捨てて buffered を作り直す。
503pub(crate) fn source_buffer_remove(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
504    let (start, end) = (arg(a, 0).to_number(), arg(a, 1).to_number());
505    if let Value::Object(sb_obj) = &this {
506        let id = sb_obj.borrow().props.get("_sb_id").map_or(0, |v| v.to_number() as usize);
507        let tr = {
508            let mut list = SOURCE_BUFFERS.lock();
509            list.iter_mut().find(|b| b.id == id).map(|core| {
510                core.remove_range(start, end);
511                core.buffered.clone()
512            })
513        };
514        sb_obj
515            .borrow_mut()
516            .props
517            .insert("buffered".into(), make_time_ranges_object(&tr.unwrap_or_default()));
518        // 完了イベントは appendBuffer と同じく次のタスクで。
519        sb_obj.borrow_mut().props.insert("updating".into(), Value::Bool(true));
520        schedule_sb_completion(it, &this, true);
521    }
522    Ok(Value::Undefined)
523}
524
525/// `TimeRanges` JS オブジェクトを構築
526fn make_time_ranges_object(tr: &TimeRanges) -> Value {
527    let obj = Obj::plain();
528    let starts = Obj::array(tr.ranges.iter().map(|r| Value::Number(r.start)).collect());
529    let ends = Obj::array(tr.ranges.iter().map(|r| Value::Number(r.end)).collect());
530    {
531        let mut o = obj.borrow_mut();
532        o.props.insert("length".into(), Value::Number(tr.len() as f64));
533        o.props.insert("_starts".into(), Value::Object(starts));
534        o.props.insert("_ends".into(), Value::Object(ends));
535        o.props.insert("start".into(), nv("TimeRanges.start", time_ranges_start));
536        o.props.insert("end".into(), nv("TimeRanges.end", time_ranges_end));
537    }
538    Value::Object(obj)
539}
540
541pub(crate) fn time_ranges_start(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
542    let idx = arg(a, 0).to_number() as usize;
543    if let Value::Object(o) = &this {
544        if let Some(Value::Object(arr)) = o.borrow().props.get("_starts") {
545            if let ObjKind::Array(ref vec) = arr.borrow().kind {
546                if let Some(v) = vec.get(idx) {
547                    return Ok(v.clone());
548                }
549            }
550        }
551    }
552    Ok(Value::Undefined)
553}
554
555pub(crate) fn time_ranges_end(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
556    let idx = arg(a, 0).to_number() as usize;
557    if let Value::Object(o) = &this {
558        if let Some(Value::Object(arr)) = o.borrow().props.get("_ends") {
559            if let ObjKind::Array(ref vec) = arr.borrow().kind {
560                if let Some(v) = vec.get(idx) {
561                    return Ok(v.clone());
562                }
563            }
564        }
565    }
566    Ok(Value::Undefined)
567}