Skip to main content

atmos/os_lib/web_engine/
video.rs

1// web_engine/video.rs - Video playback state & background thread
2
3use alloc::string::String;
4use alloc::vec::Vec;
5use spin::Mutex;
6
7#[derive(Clone)]
8pub(super) struct VideoPlayState {
9    pub src_url: String,
10    pub x: u32,
11    pub y: u32,
12    pub width: u32,
13    pub height: u32,
14    pub playing: bool,
15    pub stop_request: bool,
16    /// 一時停止中(MSE の再生のみ。スレッドは位置と最後のフレームを保ったまま待つ)。
17    pub paused: bool,
18}
19
20pub(super) static VIDEO_STATE: Mutex<Option<VideoPlayState>> = Mutex::new(None);
21
22#[derive(Clone)]
23pub(super) struct SharedVideoFrame {
24    pub width: u32,
25    pub height: u32,
26    pub rgb_data: Vec<u8>,
27}
28
29pub(super) static CURRENT_VIDEO_FRAME: Mutex<Option<SharedVideoFrame>> = Mutex::new(None);
30
31/// 動画レイヤ: 最後にアプリが `<video>` を描いた位置(ウィンドウのバッファ座標)と描画先。
32///
33/// 【2026-10-18】アプリのスレッドが再レイアウトや JS で詰まっている間(YouTube の操作部の
34/// 全再レイアウトは QEMU で 1 回約 2 秒)、動画のフレームが画面に出ず止まって見えた。
35/// 実ブラウザが動画を別の層として合成するのと同じく、アプリの描画が `STALL_MS` 以上
36/// 途絶えている間は、再生スレッドがウィンドウの共有バッファの動画の矩形へ直接書く。
37/// アプリが描いている間はアプリに任せる(操作部などの重なりを正しく保つため)。
38pub(super) struct VideoLayer {
39    pub target: crate::kernel::window_mgr::DrawTarget,
40    /// フレームを置く矩形(縦横比を保った後)`(x, y, w, h)`。
41    pub dst: (i32, i32, u32, u32),
42    /// 見えている範囲(クリップ)`(x0, y0, x1, y1)`。
43    pub clip: (u32, u32, u32, u32),
44    /// アプリが最後に動画を描いた時刻 [ms]。
45    pub last_app_draw_ms: u64,
46}
47
48pub(super) static VIDEO_LAYER: Mutex<Option<VideoLayer>> = Mutex::new(None);
49
50/// アプリの描画がこれ以上途絶えたら、再生スレッドが直接書く [ms]。
51const STALL_MS: u64 = 150;
52
53/// アプリが `<video>` を描いたことを記録する(描画中のアプリのスレッドから呼ぶ)。
54pub(super) fn note_app_video_draw(dst: (i32, i32, u32, u32), clip: (u32, u32, u32, u32)) {
55    let Some(target) = crate::kernel::window_mgr::current_draw_target() else { return };
56    let now = crate::kernel::timer::get_system_time_ms();
57    *VIDEO_LAYER.lock() = Some(VideoLayer { target, dst, clip, last_app_draw_ms: now });
58}
59
60/// アプリの描画が詰まっていれば、フレームを共有バッファへ直接書いて画面へ出す(再生スレッドから呼ぶ)。
61fn blit_if_app_stalled(frame: &SharedVideoFrame) {
62    let now = crate::kernel::timer::get_system_time_ms();
63    let layer = VIDEO_LAYER.lock();
64    let Some(l) = layer.as_ref() else { return };
65    if now.wrapping_sub(l.last_app_draw_ms) < STALL_MS || frame.width == 0 || frame.height == 0 {
66        return;
67    }
68    let (dx, dy, dw, dh) = l.dst;
69    let (bw, bh) = (l.target.width, l.target.height);
70    let x0 = (dx.max(0) as u32).max(l.clip.0);
71    let y0 = (dy.max(0) as u32).max(l.clip.1);
72    let x1 = ((dx + dw as i32).max(0) as u32).min(l.clip.2).min(bw);
73    let y1 = ((dy + dh as i32).max(0) as u32).min(l.clip.3).min(bh);
74    if x0 >= x1 || y0 >= y1 || dw == 0 || dh == 0 {
75        return;
76    }
77    let mut buf = l.target.buffer.lock();
78    if buf.len() < (bw * bh) as usize {
79        return;
80    }
81    for y in y0..y1 {
82        let sy = ((y as i32 - dy) as u32 * frame.height / dh).min(frame.height - 1);
83        let row = (y * bw) as usize;
84        for x in x0..x1 {
85            let sx = ((x as i32 - dx) as u32 * frame.width / dw).min(frame.width - 1);
86            let i = ((sy * frame.width + sx) * 3) as usize;
87            if let (Some(&r), Some(&g), Some(&b)) = (frame.rgb_data.get(i), frame.rgb_data.get(i + 1), frame.rgb_data.get(i + 2)) {
88                // 画面のバッファは 0xFF_BB_GG_RR(`Screen::draw_scanline_colors` と同じ)。
89                buf[row + x as usize] = 0xFF00_0000 | ((b as u32) << 16) | ((g as u32) << 8) | r as u32;
90            }
91        }
92    }
93    l.target.updated.store(true, core::sync::atomic::Ordering::Release);
94}
95
96pub(super) fn stop_video_playback() {
97    *VIDEO_LAYER.lock() = None;
98    crate::kernel::audio::AudioStream::stop();
99    let mut state = VIDEO_STATE.lock();
100    if let Some(ref mut st) = *state {
101        st.stop_request = true;
102    }
103    crate::kernel::audio::Audio::mute();
104    crate::kernel::audio::Audio::stop_mml();
105}
106
107pub(super) fn video_playback_thread() {
108    let state_opt = {
109        let state = VIDEO_STATE.lock();
110        state.clone()
111    };
112
113    let state = match state_opt {
114        Some(s) => s,
115        None => return,
116    };
117
118    let url = state.src_url.clone();
119    {
120        let mut pb = crate::os_lib::mse::PLAYBACK.lock();
121        let resume = pb.should_resume(&url);
122        pb.url = url.clone();
123        pb.playing = true;
124        pb.ended = false;
125        pb.ended_pending = false;
126        if resume {
127            let mut seek = crate::os_lib::mse::SEEK_REQUEST.lock();
128            if seek.is_none() {
129                *seek = Some(pb.current_time);
130            }
131        } else {
132            pb.current_time = 0.0;
133            pb.total_video_frames = 0;
134            pb.dropped_video_frames = 0;
135            *CURRENT_VIDEO_FRAME.lock() = None;
136        }
137    }
138    if url.starts_with("blob:mediasource-") {
139        play_media_source(&url);
140        finish_playback();
141    }
142    let (is_https, host, path) = match super::parse_url_scheme(&url) {
143        Some(res) => res,
144        None => {
145            crate::println!("Video Error: Invalid URL (requires http:// or https://): {}", url);
146            return;
147        }
148    };
149
150    crate::println!(
151        "Video Loader: Downloading video via {} from {}{} ...",
152        if is_https { "HTTPS" } else { "HTTP" },
153        host,
154        path
155    );
156    let video_data = if is_https {
157        match crate::kernel::tls::https_get_binary(&host, &path) {
158            Ok(data) => data,
159            Err(e) => {
160                crate::println!("Video Error: Failed to download HTTPS video: {}", e);
161                return;
162            }
163        }
164    } else {
165        match crate::kernel::net::http_get_binary(&host, &path) {
166            Ok(data) => data,
167            Err(e) => {
168                crate::println!("Video Error: Failed to download HTTP video: {}", e);
169                return;
170            }
171        }
172    };
173    crate::println!("Video Loader: Downloaded {} bytes.", video_data.len());
174
175    let demuxer = match crate::os_lib::mp4::Mp4Demuxer::parse(&video_data) {
176        Ok(d) => d,
177        Err(e) => {
178            crate::println!("Video Error: MP4 parse failed: {}", e);
179            return;
180        }
181    };
182
183    crate::println!(
184        "Video Loader: Video track samples: {}, Audio track samples: {}",
185        demuxer.video_samples.len(),
186        demuxer.audio_samples.len()
187    );
188
189    // 本物の H.264 baseline デコーダ(h264_decode)。avcC から SPS/PPS を初期化。
190    let mut h264_decoder = crate::os_lib::h264_decode::VideoDecoder::new();
191    if let Some(ref avcc) = demuxer.video_avcc {
192        h264_decoder.init_avcc(avcc);
193        crate::println!("Video Loader: avcC initialized into H.264 decoder");
194    } else {
195        crate::println!("Video Loader: no avcC (will rely on in-band SPS/PPS)");
196    }
197    // 音声: MP4 の音声は AAC(mp4a、esds の AudioSpecificConfig で初期化)。
198    // 【2026-10-17】従来は中身の無い MP3 デコーダに渡しており、音が出ていなかった。
199    let mut full_pcm = alloc::vec::Vec::new();
200    let aac = demuxer
201        .audio_asc
202        .as_deref()
203        .ok_or("no AudioSpecificConfig")
204        .and_then(crate::os_lib::aac::parse_audio_specific_config)
205        .and_then(crate::os_lib::aac::AacDecoder::new);
206    let mut audio_fmt: Option<(u32, u32)> = None;
207    match aac {
208        Ok(mut dec) => {
209            let cfg = dec.config();
210            audio_fmt = Some((cfg.sample_rate, cfg.channels));
211            crate::println!(
212                "Video Loader: AAC {} Hz, {} ch, {} frames",
213                cfg.sample_rate,
214                cfg.channels,
215                demuxer.audio_samples.len()
216            );
217            for sample in &demuxer.audio_samples {
218                let Some(sample_data) = video_data.get(sample.offset..sample.offset + sample.size) else {
219                    continue;
220                };
221                match dec.decode_frame(sample_data) {
222                    Ok(pcm) => full_pcm.extend(pcm),
223                    Err(e) => crate::println!("Video Loader: AAC frame error: {}", e),
224                }
225            }
226        }
227        Err(e) => {
228            if !demuxer.audio_samples.is_empty() {
229                crate::println!("Video Loader: audio not decoded: {}", e);
230            }
231        }
232    }
233
234    if !full_pcm.is_empty() {
235        let (vol, muted) = {
236            let pb = crate::os_lib::mse::PLAYBACK.lock();
237            (pb.volume, pb.muted)
238        };
239        crate::os_lib::mse::scale_pcm_samples(&mut full_pcm, vol, muted);
240        if !muted && vol > 0.0 {
241            crate::println!(
242                "Video Player: Starting audio playback ({} samples) ...",
243                full_pcm.len()
244            );
245            // 【2026-10-19】連続再生(PWM の FIFO+DMA のリング)で全体を流す。
246            if let Some((rate, ch)) = audio_fmt {
247                crate::kernel::audio::AudioStream::start(rate, ch);
248                crate::kernel::audio::AudioStream::push(&full_pcm);
249            }
250        }
251    }
252
253    let start_time = crate::kernel::timer::get_system_time_ms();
254    let mut video_sample_idx = 0;
255
256    while video_sample_idx < demuxer.video_samples.len() {
257        {
258            let s = VIDEO_STATE.lock();
259            if s.as_ref().is_none_or(|state| state.stop_request) {
260                break;
261            }
262        }
263
264        let sample = &demuxer.video_samples[video_sample_idx];
265        let elapsed = crate::kernel::timer::get_system_time_ms().wrapping_sub(start_time);
266
267        if elapsed >= sample.timestamp_ms {
268            let sample_data = &video_data[sample.offset..sample.offset + sample.size];
269            let decoded = h264_decoder.decode_sample_rgb(sample_data);
270            {
271                let mut pb = crate::os_lib::mse::PLAYBACK.lock();
272                pb.current_time = sample.timestamp_ms as f64 / 1000.0;
273                if decoded.is_some() {
274                    pb.total_video_frames = pb.total_video_frames.saturating_add(1);
275                } else {
276                    pb.dropped_video_frames = pb.dropped_video_frames.saturating_add(1);
277                }
278            }
279            if let Some((fw, fh, rgb)) = decoded {
280                let f = SharedVideoFrame { width: fw, height: fh, rgb_data: rgb };
281                blit_if_app_stalled(&f);
282                *CURRENT_VIDEO_FRAME.lock() = Some(f);
283            }
284            video_sample_idx += 1;
285        } else {
286            let sleep_ms = (sample.timestamp_ms - elapsed).min(10);
287            if sleep_ms > 0 {
288                crate::kernel::scheduler::sleep((sleep_ms / 10).max(1) as usize);
289            } else {
290                crate::kernel::scheduler::yield_now();
291            }
292        }
293    }
294
295    finish_playback();
296}
297
298/// 再生の後始末をしてスレッドを終える。
299fn finish_playback() -> ! {
300    // 止められた(pause)のでなく最後まで再生したときだけ ended。
301    let stopped = VIDEO_STATE.lock().as_ref().is_none_or(|st| st.stop_request);
302    {
303        let mut pb = crate::os_lib::mse::PLAYBACK.lock();
304        pb.playing = false;
305        if !stopped {
306            pb.ended = true;
307            pb.ended_pending = true;
308        }
309    }
310    {
311        let mut s = VIDEO_STATE.lock();
312        if let Some(ref mut st) = *s {
313            st.playing = false;
314        }
315    }
316    // 一時停止(stopped == true)のときは最後のフレームを画面に残す。
317    if !stopped {
318        let mut current_frame = CURRENT_VIDEO_FRAME.lock();
319        *current_frame = None;
320    }
321    crate::println!("Video Player: Playback finished.");
322    // 【2026-08-05】スレッドの入口関数は**関数末尾で終わらない**。
323    //
324    // カーネルスレッドは入口から再実行され続ける。
325    // フォント/画像のワーカーで同じ形の欠陥があり、実測で
326    // **3 億 4 千万回**空回りしていた(`spec/resource_loading.md` R-17)。
327    // 再生が終わったら明示的に終了する。
328    crate::kernel::scheduler::exit();
329}
330
331fn stop_requested() -> bool {
332    VIDEO_STATE.lock().as_ref().is_none_or(|st| st.stop_request)
333}
334
335/// MSE(`src = URL.createObjectURL(mediaSource)`)の再生。JS が appendBuffer した fMP4 のサンプルを
336/// `SOURCE_BUFFERS` から復号時刻順に取り出し、最初のサンプルを基準に実時間で表示する。
337/// 【2026-10-17】新設。従来は blob URL を HTTP の URL として扱えず再生できなかった。
338/// サンプルがまだ届いていなければ待つ。endOfStream 後に尽きるか、15 秒届かなければ終わる。
339fn play_media_source(url: &str) {
340    use crate::os_lib::fmp4::TrackKind;
341    use crate::os_lib::js::builtins::{media_source_for_url, SOURCE_BUFFERS};
342    let Some((ms_id, _)) = media_source_for_url(url) else {
343        crate::warn!("[VIDEO] unknown MediaSource URL: {}", url);
344        return;
345    };
346    let now = crate::kernel::timer::get_system_time_ms;
347    let started_at = now();
348    crate::info!("[VIDEO] MSE 再生開始 t={}ms", started_at);
349    let mut dec = crate::os_lib::h264_decode::VideoDecoder::new();
350    let mut inited = false;
351    let mut last_dts: Option<u64> = None;
352    let mut clock: Option<(u64, u64)> = None; // (開始した実時刻, 最初のサンプルの時刻) ミリ秒
353    let mut seek_target: Option<f64> = None; // シーク時、この時刻(秒)に達するまでは表示・待機せず早送り復号する
354    let mut last_progress = now();
355    let mut aac_dec: Option<crate::os_lib::aac::AacDecoder> = None;
356    let mut last_audio_dts: Option<u64> = None;
357    let mut frames = 0usize;
358    let mut was_waiting = false;
359    while !stop_requested() {
360        // 一時停止: 位置と最後のフレームを保って待ち、再開したら次のサンプルから時計を取り直す。
361        if VIDEO_STATE.lock().as_ref().is_some_and(|st| st.paused) {
362            crate::os_lib::mse::PLAYBACK.lock().playing = false;
363            crate::kernel::audio::AudioStream::set_paused(true);
364            clock = None;
365            crate::kernel::scheduler::sleep(5);
366            last_progress = now();
367            continue;
368        }
369        crate::os_lib::mse::PLAYBACK.lock().playing = true;
370        crate::kernel::audio::AudioStream::set_paused(false);
371        // シーク: 目標時刻以前の最後のキーフレームから復号し直す(デコーダも作り直す)。
372        if let Some(target) = crate::os_lib::mse::SEEK_REQUEST.lock().take() {
373            let (key, audio_prev_dts) = {
374                let list = SOURCE_BUFFERS.lock();
375                let v_key = list.iter().filter(|b| b.owner == ms_id).find_map(|b| {
376                    let t = b.demux.tracks.iter().find(|t| t.kind == TrackKind::Video)?;
377                    let ts = t.timescale.max(1) as f64;
378                    b.samples
379                        .iter()
380                        .filter(|s| s.track_id == t.track_id && s.keyframe && s.pts as f64 / ts <= target + 0.001)
381                        .max_by_key(|s| s.dts)
382                        .map(|s| s.dts)
383                });
384                let a_prev = list.iter().filter(|b| b.owner == ms_id).find_map(|b| {
385                    let t = b.demux.tracks.iter().find(|t| t.kind == TrackKind::Audio)?;
386                    let ts = t.timescale.max(1) as f64;
387                    b.samples
388                        .iter()
389                        .filter(|s| s.track_id == t.track_id && (s.pts.max(0) as f64) / ts < target)
390                        .max_by_key(|s| s.dts)
391                        .map(|s| s.dts)
392                });
393                (v_key, a_prev)
394            };
395            last_dts = key.and_then(|d| d.checked_sub(1));
396            last_audio_dts = audio_prev_dts;
397            // 古い位置の音声は捨てる(シーク先から鳴らし直す)。
398            crate::kernel::audio::AudioStream::flush();
399            clock = None;
400            seek_target = Some(target);
401            dec = crate::os_lib::h264_decode::VideoDecoder::new();
402            inited = false;
403            crate::os_lib::mse::PLAYBACK.lock().current_time = target;
404        }
405        // 次の映像サンプル(復号時刻が前回より後で最小のもの)を取り出す。
406        let next = {
407            let list = SOURCE_BUFFERS.lock();
408            list.iter().filter(|b| b.owner == ms_id).find_map(|b| {
409                let t = b.demux.tracks.iter().find(|t| t.kind == TrackKind::Video)?;
410                let s = b
411                    .samples
412                    .iter()
413                    .filter(|s| s.track_id == t.track_id && last_dts.is_none_or(|d| s.dts > d))
414                    .min_by_key(|s| s.dts)?;
415                Some((t.avcc.clone(), t.timescale.max(1) as u64, s.clone()))
416            })
417        };
418        let Some((avcc, ts, sample)) = next else {
419            let ended = media_source_for_url(url).is_some_and(|x| x.1);
420            let elapsed = now().wrapping_sub(last_progress);
421            if ended || elapsed > 15_000 {
422                break;
423            }
424            if !was_waiting {
425                crate::os_lib::mse::PLAYBACK.lock().waiting_pending = true;
426                was_waiting = true;
427            } else if elapsed > 5_000 {
428                crate::os_lib::mse::PLAYBACK.lock().stalled_pending = true;
429            }
430            crate::kernel::scheduler::sleep(2);
431            continue;
432        };
433        if was_waiting {
434            crate::os_lib::mse::PLAYBACK.lock().playing_pending = true;
435            was_waiting = false;
436        }
437        if !inited {
438            if let Some(a) = avcc {
439                dec.init_avcc(&a);
440            }
441            inited = true;
442        }
443        let pts_sec = sample.pts.max(0) as f64 / ts as f64;
444        // シーク目標時刻より手前の参照フレームは待機も画面更新もせず復号だけ進める
445        if let Some(target) = seek_target {
446            if pts_sec + 0.001 < target {
447                let _ = dec.decode_sample_rgb(&sample.data);
448                last_dts = Some(sample.dts);
449                last_progress = now();
450                continue;
451            }
452            seek_target = None;
453        }
454        // 現在の映像時刻に合わせて音声サンプルをストリーミング供給する
455        // 先読みは 1 秒(映像の時刻 P の音声を P-1s で足し、待ち行列が実時間で減るので鳴るのは P 付近)。
456        // 【2026-10-19】0.35 秒では映像の復号の待ちの間に待ち行列が空になり、音が途切れた。
457        feed_mse_audio_window(ms_id, &mut aac_dec, &mut last_audio_dts, pts_sec + 1.0);
458
459        let rate = crate::os_lib::mse::PLAYBACK.lock().playback_rate.clamp(0.25, 4.0);
460        let t_ms = sample.dts * 1000 / ts;
461        let (c0, t0) = *clock.get_or_insert((now(), t_ms));
462        let scaled_delta = ((t_ms.saturating_sub(t0) as f64) / rate) as u64;
463        let due = c0 + scaled_delta;
464        let cur = now();
465        if cur < due {
466            crate::kernel::scheduler::sleep(((due - cur) / 10).clamp(1, 5) as usize);
467            continue;
468        }
469        let decoded = dec.decode_sample_rgb(&sample.data);
470        if let Some((fw, fh, rgb)) = decoded {
471            let f = SharedVideoFrame { width: fw, height: fh, rgb_data: rgb };
472            blit_if_app_stalled(&f);
473            *CURRENT_VIDEO_FRAME.lock() = Some(f);
474            frames += 1;
475            let mut pb = crate::os_lib::mse::PLAYBACK.lock();
476            pb.current_time = pts_sec;
477            pb.total_video_frames = pb.total_video_frames.saturating_add(1);
478        } else {
479            let mut pb = crate::os_lib::mse::PLAYBACK.lock();
480            pb.current_time = pts_sec;
481            pb.dropped_video_frames = pb.dropped_video_frames.saturating_add(1);
482        }
483        last_dts = Some(sample.dts);
484        last_progress = now();
485    }
486    crate::kernel::audio::AudioStream::stop();
487    crate::info!("[VIDEO] MSE 再生終了 frames={} elapsed={}ms", frames, now().wrapping_sub(started_at));
488}
489
490/// MSE の音声トラックから `max_pts_sec` までの未再生 AAC フレームを最大 16 個取り出して復号し、
491/// 音量・ミュート設定を反映して連続再生(`AudioStream`)の待ち行列へ足す。
492fn feed_mse_audio_window(
493    ms_id: usize,
494    aac_dec: &mut Option<crate::os_lib::aac::AacDecoder>,
495    last_audio_dts: &mut Option<u64>,
496    max_pts_sec: f64,
497) {
498    use crate::os_lib::fmp4::TrackKind;
499    use crate::os_lib::js::builtins::SOURCE_BUFFERS;
500    let (asc, frames, next_dts) = {
501        let list = SOURCE_BUFFERS.lock();
502        let Some((b, t)) = list.iter().filter(|b| b.owner == ms_id).find_map(|b| {
503            let t = b.demux.tracks.iter().find(|t| t.kind == TrackKind::Audio)?;
504            Some((b, t))
505        }) else {
506            return;
507        };
508        let (frames, next_dts) = crate::os_lib::mse::select_audio_samples_in_window(
509            &b.samples,
510            t.track_id,
511            t.timescale,
512            *last_audio_dts,
513            max_pts_sec,
514            16,
515        );
516        (t.asc.clone(), frames, next_dts)
517    };
518    if frames.is_empty() {
519        return;
520    }
521    *last_audio_dts = next_dts;
522    if aac_dec.is_none() {
523        if let Some(asc_bytes) = asc.as_deref() {
524            if let Ok(cfg) = crate::os_lib::aac::parse_audio_specific_config(asc_bytes) {
525                if let Ok(dec) = crate::os_lib::aac::AacDecoder::new(cfg) {
526                    // 【2026-10-19】最初の復号で連続再生を始める(以後は待ち行列へ足すだけ)。
527                    crate::kernel::audio::AudioStream::start(cfg.sample_rate, cfg.channels);
528                    *aac_dec = Some(dec);
529                }
530            }
531        }
532    }
533    let Some(dec) = aac_dec.as_mut() else {
534        return;
535    };
536    let (vol, muted) = {
537        let pb = crate::os_lib::mse::PLAYBACK.lock();
538        (pb.volume, pb.muted)
539    };
540    if muted || vol <= 0.0 {
541        // ミュート中もデコーダの内部状態を保つため復号だけは進める
542        for f in &frames {
543            let _ = dec.decode_frame(f);
544        }
545        return;
546    }
547    let mut pcm = Vec::new();
548    for f in &frames {
549        if let Ok(p) = dec.decode_frame(f) {
550            pcm.extend(p);
551        }
552    }
553    if !pcm.is_empty() {
554        crate::os_lib::mse::scale_pcm_samples(&mut pcm, vol, muted);
555        crate::kernel::audio::AudioStream::push(&pcm);
556    }
557}