Compare commits
23 commits
pre-1.7-me
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e55e68c08 | |||
| 1d3445b1c3 | |||
|
|
900453787f | ||
|
|
a6af9a48c2 | ||
|
|
882d16513b | ||
|
|
8923105997 | ||
|
|
f62353fd0a | ||
|
|
8ad21891b3 | ||
| 3ee781bfc2 | |||
| cfdeaa3ae1 | |||
|
|
3b016ff126 | ||
|
|
66b3f34335 | ||
|
|
3d8af19ac4 | ||
| 78b806a347 | |||
| 45c9b1aa19 | |||
|
|
a297c24a96 | ||
|
|
f0365d411b | ||
|
|
e040f7fbc7 | ||
| 89dd5e1f10 | |||
| 3171217e5a | |||
|
|
c9cba75c84 | ||
|
|
23254703eb | ||
|
|
b5f2fffe65 |
21 changed files with 686 additions and 603 deletions
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,8 +1,10 @@
|
||||||
- [ ] I have disclosed use of any AI generated code in my commit messages.
|
Replace this text with a description of your changes, and why these changes are important. Read and acknowledge the list below. PRs without a completed checkbox will be closed.
|
||||||
- If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR.
|
|
||||||
- In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment.
|
|
||||||
- [ ] I understand these changes in full and will be able to respond to review comments.
|
|
||||||
- [ ] My change is accurately described in the commit message.
|
|
||||||
- [ ] My contribution is tested and working as described.
|
|
||||||
- [ ] I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- I have not included any LLM (also known as AI) generated content in this PR, including code, comments, and descriptions.
|
||||||
|
- I understand these changes in full and will be able to respond to review comments.
|
||||||
|
- My change is accurately described in the commit message.
|
||||||
|
- My contribution is tested and working as described.
|
||||||
|
- I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.
|
||||||
|
- [ ] I have read and followed all of the above requirements. For more information, see our [contributor guide](https://github.com/pop-os/pop/blob/master/CONTRIBUTING.md).
|
||||||
|
|
|
||||||
14
.github/workflows/require-checklist.yml
vendored
Normal file
14
.github/workflows/require-checklist.yml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# From https://github.com/mheap/require-checklist-action, license MIT
|
||||||
|
name: Require Checklist
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: mheap/require-checklist-action@v2
|
||||||
|
with:
|
||||||
|
requireChecklist: true
|
||||||
984
Cargo.lock
generated
984
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-player"
|
name = "cosmic-player"
|
||||||
version = "1.6.0"
|
version = "1.10.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.93"
|
rust-version = "1.93"
|
||||||
|
|
||||||
|
|
|
||||||
70
cargo.just
Normal file
70
cargo.just
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
cargo-target-dir := env('CARGO_TARGET_DIR', 'target')
|
||||||
|
|
||||||
|
# Compile with debug profile
|
||||||
|
[no-cd]
|
||||||
|
build-debug *args:
|
||||||
|
cargo build {{args}}
|
||||||
|
|
||||||
|
# Compile with release profile
|
||||||
|
[no-cd]
|
||||||
|
build-release *args: (build-debug '--release' args)
|
||||||
|
|
||||||
|
# Compile with a vendored tarball
|
||||||
|
[no-cd]
|
||||||
|
build-vendored *args: vendor-extract (build-release '--frozen --offline' args)
|
||||||
|
|
||||||
|
# Check for errors and linter warnings
|
||||||
|
[no-cd]
|
||||||
|
check *args:
|
||||||
|
cargo clippy --all-features {{args}} -- -W clippy::pedantic
|
||||||
|
|
||||||
|
# Runs a check with JSON message format for IDE integration
|
||||||
|
[no-cd]
|
||||||
|
check-json: (check '--message-format=json')
|
||||||
|
|
||||||
|
# Remove Cargo build artifacts
|
||||||
|
[no-cd]
|
||||||
|
clean:
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
# Also remove .cargo and vendored dependencies
|
||||||
|
[no-cd]
|
||||||
|
clean-dist: clean
|
||||||
|
rm -rf .cargo vendor vendor.tar target
|
||||||
|
|
||||||
|
# Run the application for testing purposes
|
||||||
|
[no-cd]
|
||||||
|
run *args:
|
||||||
|
cargo run {{args}}
|
||||||
|
|
||||||
|
# Run `cargo test`
|
||||||
|
[no-cd]
|
||||||
|
test *args:
|
||||||
|
cargo test {{args}}
|
||||||
|
|
||||||
|
# Vendor Cargo dependencies locally
|
||||||
|
[no-cd]
|
||||||
|
vendor:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
mkdir -p .cargo
|
||||||
|
cargo vendor --locked | head -n -1 > .cargo/config.toml
|
||||||
|
echo 'directory = "vendor"' >> .cargo/config.toml
|
||||||
|
echo >> .cargo/config.toml
|
||||||
|
echo '[env]' >> .cargo/config.toml
|
||||||
|
if [ -n "${SOURCE_DATE_EPOCH}" ]
|
||||||
|
then
|
||||||
|
source_date="$(date -d "@${SOURCE_DATE_EPOCH}" "+%Y-%m-%d")"
|
||||||
|
echo "VERGEN_GIT_COMMIT_DATE = \"${source_date}\"" >> .cargo/config.toml
|
||||||
|
fi
|
||||||
|
if [ -n "${SOURCE_GIT_HASH}" ]
|
||||||
|
then
|
||||||
|
echo "VERGEN_GIT_SHA = \"${SOURCE_GIT_HASH}\"" >> .cargo/config.toml
|
||||||
|
fi
|
||||||
|
tar pcf vendor.tar .cargo vendor
|
||||||
|
rm -rf .cargo vendor
|
||||||
|
|
||||||
|
# Extracts vendored dependencies
|
||||||
|
[no-cd]
|
||||||
|
vendor-extract:
|
||||||
|
rm -rf vendor
|
||||||
|
tar pxf vendor.tar
|
||||||
24
debian/changelog
vendored
24
debian/changelog
vendored
|
|
@ -1,3 +1,27 @@
|
||||||
|
cosmic-player (1.10.0) noble; urgency=medium
|
||||||
|
|
||||||
|
* Epoch 1.10.0 version update
|
||||||
|
|
||||||
|
-- Michael Murphy <michael@mmurphy.dev> Mon, 05 Oct 2026 21:25:27 +0200
|
||||||
|
|
||||||
|
cosmic-player (1.9.0) noble; urgency=medium
|
||||||
|
|
||||||
|
* Epoch 1.9.0 version update
|
||||||
|
|
||||||
|
-- Michael Murphy <michael@mmurphy.dev> Mon, 14 Sep 2026 21:57:45 +0200
|
||||||
|
|
||||||
|
cosmic-player (1.8.0) noble; urgency=medium
|
||||||
|
|
||||||
|
* Epoch 1.8.0 version update
|
||||||
|
|
||||||
|
-- Jeremy Soller <jeremy@system76.com> Wed, 02 Sep 2026 08:15:23 -0600
|
||||||
|
|
||||||
|
cosmic-player (1.7.0) noble; urgency=medium
|
||||||
|
|
||||||
|
* Epoch 1.7.0 version update
|
||||||
|
|
||||||
|
-- Jeremy Soller <jeremy@system76.com> Tue, 25 Aug 2026 09:20:51 -0600
|
||||||
|
|
||||||
cosmic-player (1.6.0) noble; urgency=medium
|
cosmic-player (1.6.0) noble; urgency=medium
|
||||||
|
|
||||||
* Epoch 1.6.0 version update
|
* Epoch 1.6.0 version update
|
||||||
|
|
|
||||||
6
debian/source/options
vendored
6
debian/source/options
vendored
|
|
@ -1,4 +1,6 @@
|
||||||
tar-ignore=.github
|
tar-ignore=.github
|
||||||
tar-ignore=.vscode
|
tar-ignore=target/.rustc_info.json
|
||||||
|
tar-ignore=target/CACHEDIR.TAG
|
||||||
|
tar-ignore=target/debug
|
||||||
|
tar-ignore=target/release
|
||||||
tar-ignore=vendor
|
tar-ignore=vendor
|
||||||
tar-ignore=target
|
|
||||||
|
|
@ -9,3 +9,28 @@ settings = Indstillinger
|
||||||
open-file = Åbn fil
|
open-file = Åbn fil
|
||||||
open-folder = Åbn mappe
|
open-folder = Åbn mappe
|
||||||
file = Fil
|
file = Fil
|
||||||
|
album = Album: { $album }
|
||||||
|
audio = Lyd
|
||||||
|
no-video-or-audio-file-open = Ingen video- eller lydfil åben
|
||||||
|
off = Fra
|
||||||
|
subtitles = Undertekster
|
||||||
|
unknown-author = Ukendt Forfatter
|
||||||
|
untitled = Utituleret
|
||||||
|
open-media = Åbn medie...
|
||||||
|
open-recent-media = Åbn seneste medie
|
||||||
|
clear-recent = Ryd seneste-liste
|
||||||
|
open-media-folder = Åbn mediemappe...
|
||||||
|
open-recent-media-folder = Åbn seneste mediemappe
|
||||||
|
close-media-folder = Luk mediemappe
|
||||||
|
repeat-disabled = Gentagelse deaktiveret
|
||||||
|
repeat-track = Gentag nummer
|
||||||
|
playback = Afspilning
|
||||||
|
next-frame = Næste billede
|
||||||
|
previous-frame = Forrige billede
|
||||||
|
ab-repeat = A-B-gentagelse
|
||||||
|
ab-repeat-set-a = A-B-gentagelse (A)
|
||||||
|
ab-repeat-set-b = A-B-gentagelse (B)
|
||||||
|
ab-repeat-clear = Ryd A-B-gentagelse
|
||||||
|
xdg-name = COSMIC Medieafspiller
|
||||||
|
xdg-comment = Medieafspiller til COSMIC-skrivebordet
|
||||||
|
xdg-keywords = Lyd;Film;Film;Musik;Lyd;Video;
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,6 @@ ab-repeat = A-B-Wiederholung
|
||||||
ab-repeat-set-a = A-B-Wiederholung (A)
|
ab-repeat-set-a = A-B-Wiederholung (A)
|
||||||
ab-repeat-set-b = A-B-Wiederholung (B)
|
ab-repeat-set-b = A-B-Wiederholung (B)
|
||||||
ab-repeat-clear = A-B-Wiederholung löschen
|
ab-repeat-clear = A-B-Wiederholung löschen
|
||||||
xdg-keywords = Audio;Film;Musik;Ton;Video;
|
xdg-keywords = Audio;Film;Musik;Klang;Ton;Video;
|
||||||
xdg-name = COSMIC Mediaplayer
|
xdg-name = COSMIC Mediaplayer
|
||||||
xdg-comment = Mediaplayer für den COSMIC-Desktop
|
xdg-comment = Mediaplayer für den COSMIC-Desktop
|
||||||
|
|
|
||||||
0
i18n/fil/cosmic_player.ftl
Normal file
0
i18n/fil/cosmic_player.ftl
Normal file
|
|
@ -1,7 +1,7 @@
|
||||||
settings = Axustes
|
settings = Axustes
|
||||||
appearance = Apariencia
|
appearance = Apariencia
|
||||||
theme = Tema
|
theme = Tema
|
||||||
match-desktop = Axustar ao escritorio
|
match-desktop = Emparexar co escritorio
|
||||||
dark = Escuro
|
dark = Escuro
|
||||||
light = Claro
|
light = Claro
|
||||||
file = Ficheiro
|
file = Ficheiro
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ settings = Pengaturan
|
||||||
untitled = Tidak Berjudul
|
untitled = Tidak Berjudul
|
||||||
appearance = Tampilan
|
appearance = Tampilan
|
||||||
theme = Tema
|
theme = Tema
|
||||||
match-desktop = Cocok desktop
|
match-desktop = Sesuaikan desktop
|
||||||
dark = Gelap
|
dark = Gelap
|
||||||
light = Terang
|
light = Terang
|
||||||
file = Berkas
|
file = Berkas
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ open-file = Ldi afaylu
|
||||||
open-folder = Ldi akaram
|
open-folder = Ldi akaram
|
||||||
repeat-disabled = Allus yensa
|
repeat-disabled = Allus yensa
|
||||||
repeat-track = Ales tazribt
|
repeat-track = Ales tazribt
|
||||||
quit = Tuffɣa
|
quit = Ffeɣ
|
||||||
settings = Iɣewwaṛen
|
settings = Iɣewwaṛen
|
||||||
appearance = Timeẓri
|
appearance = Timeẓri
|
||||||
theme = Asentel
|
theme = Asentel
|
||||||
dark = Aɣmayan
|
dark = Aɣmayan
|
||||||
light = Aceɛlal
|
light = Aceɛlal
|
||||||
match-desktop = Amṣada d tnarit
|
match-desktop = Semṣada akked tnarit
|
||||||
file = Afaylu
|
file = Afaylu
|
||||||
close-file = Mdel afaylu
|
close-file = Mdel afaylu
|
||||||
album = Album: { $album }
|
album = Album: { $album }
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ open-folder = Буманы ашу
|
||||||
settings = Баптаулар
|
settings = Баптаулар
|
||||||
appearance = Сыртқы көрініс
|
appearance = Сыртқы көрініс
|
||||||
theme = Тема
|
theme = Тема
|
||||||
match-desktop = Жұмыс үстеліне сәйкес болу
|
match-desktop = Жұмыс үстеліне сәйкес
|
||||||
dark = Қою
|
dark = Қараңғы
|
||||||
light = Ашық
|
light = Ашық
|
||||||
file = Файл
|
file = Файл
|
||||||
quit = Шығу
|
quit = Шығу
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ light = Ronî
|
||||||
quit = Biqedîne
|
quit = Biqedîne
|
||||||
open-file = Pelê veke
|
open-file = Pelê veke
|
||||||
open-folder = Peldankê veke
|
open-folder = Peldankê veke
|
||||||
match-desktop = Bi sermaseyê re li hev bîne
|
match-desktop = Bi sermaseyê re guncav bike
|
||||||
album = Elbum: { $album }
|
album = Elbum: { $album }
|
||||||
audio = Deng
|
audio = Deng
|
||||||
no-video-or-audio-file-open = Tu pelê vîdiyo yan jî dengê vekirî tune ne
|
no-video-or-audio-file-open = Tu pelê vîdiyo yan jî dengê vekirî tune ne
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
settings = Paramètres
|
settings = Configuracion
|
||||||
album = Album : { $album }
|
album = Album : { $album }
|
||||||
audio = Àudio
|
audio = Àudio
|
||||||
no-video-or-audio-file-open = Cap de fichièr vidèo o àudio pas dobèrt
|
no-video-or-audio-file-open = Cap de fichièr vidèo o àudio pas dobèrt
|
||||||
|
|
@ -9,7 +9,7 @@ untitled = Sens títol
|
||||||
appearance = Aparéncia
|
appearance = Aparéncia
|
||||||
theme = Tèma
|
theme = Tèma
|
||||||
match-desktop = Assortir al burèu
|
match-desktop = Assortir al burèu
|
||||||
dark = Fosc
|
dark = Escur
|
||||||
light = Clar
|
light = Clar
|
||||||
file = Fichièr
|
file = Fichièr
|
||||||
open-media = Dorbir mèdia...
|
open-media = Dorbir mèdia...
|
||||||
|
|
@ -31,3 +31,4 @@ ab-repeat-clear = Escafar la repeticion A-B
|
||||||
xdg-name = Lector multimèdia COSMIC
|
xdg-name = Lector multimèdia COSMIC
|
||||||
xdg-comment = Lector multimèdia pel burèu COSMIC
|
xdg-comment = Lector multimèdia pel burèu COSMIC
|
||||||
xdg-keywords = Àudio;Musica;Vidèo;Film;Son;
|
xdg-keywords = Àudio;Musica;Vidèo;Film;Son;
|
||||||
|
quit = Quitar
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
settings = การตั้งค่า
|
||||||
|
dark = มืด
|
||||||
|
light = สว่าง
|
||||||
|
appearance = ลักษณะ
|
||||||
|
|
@ -9,7 +9,7 @@ untitled = Без назви
|
||||||
settings = Налаштування
|
settings = Налаштування
|
||||||
appearance = Вигляд
|
appearance = Вигляд
|
||||||
theme = Тема
|
theme = Тема
|
||||||
match-desktop = Системна
|
match-desktop = Підігнати під стільницю
|
||||||
dark = Темна
|
dark = Темна
|
||||||
light = Світла
|
light = Світла
|
||||||
file = Файл
|
file = Файл
|
||||||
|
|
|
||||||
50
src/main.rs
50
src/main.rs
|
|
@ -82,7 +82,32 @@ fn get_framerate(video: &Video) -> Option<f64> {
|
||||||
let caps = pad.current_caps()?;
|
let caps = pad.current_caps()?;
|
||||||
let structure = caps.structure(0)?;
|
let structure = caps.structure(0)?;
|
||||||
let framerate = structure.get::<gst::Fraction>("framerate").ok()?;
|
let framerate = structure.get::<gst::Fraction>("framerate").ok()?;
|
||||||
Some(framerate.numer() as f64 / framerate.denom() as f64)
|
positive_framerate(framerate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn positive_framerate(framerate: gst::Fraction) -> Option<f64> {
|
||||||
|
if framerate.numer() <= 0 || framerate.denom() <= 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fps = framerate.numer() as f64 / framerate.denom() as f64;
|
||||||
|
fps.is_finite().then_some(fps)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::positive_framerate;
|
||||||
|
use iced_video_player::gst;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variable_framerate_uses_fallback() {
|
||||||
|
assert_eq!(positive_framerate(gst::Fraction::new(0, 1)), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fixed_framerate_is_preserved() {
|
||||||
|
assert_eq!(positive_framerate(gst::Fraction::new(30, 1)), Some(30.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
|
|
@ -325,6 +350,8 @@ pub enum Message {
|
||||||
VideoAreaClick,
|
VideoAreaClick,
|
||||||
PlaybackSpeed(f64),
|
PlaybackSpeed(f64),
|
||||||
ShowControls,
|
ShowControls,
|
||||||
|
ControlsHoverEnter,
|
||||||
|
ControlsHoverExit,
|
||||||
SystemThemeModeChange(cosmic_theme::ThemeMode),
|
SystemThemeModeChange(cosmic_theme::ThemeMode),
|
||||||
ToggleAbout,
|
ToggleAbout,
|
||||||
WindowClose,
|
WindowClose,
|
||||||
|
|
@ -338,6 +365,7 @@ pub struct App {
|
||||||
album_art_opt: Option<tempfile::NamedTempFile>,
|
album_art_opt: Option<tempfile::NamedTempFile>,
|
||||||
controls: bool,
|
controls: bool,
|
||||||
controls_time: Instant,
|
controls_time: Instant,
|
||||||
|
controls_hovered: bool,
|
||||||
dropdown_opt: Option<DropdownKind>,
|
dropdown_opt: Option<DropdownKind>,
|
||||||
fullscreen: bool,
|
fullscreen: bool,
|
||||||
key_binds: HashMap<KeyBind, Action>,
|
key_binds: HashMap<KeyBind, Action>,
|
||||||
|
|
@ -636,6 +664,7 @@ impl App {
|
||||||
|
|
||||||
fn update_controls(&mut self, in_use: bool) {
|
fn update_controls(&mut self, in_use: bool) {
|
||||||
if in_use
|
if in_use
|
||||||
|
|| self.controls_hovered
|
||||||
|| !self
|
|| !self
|
||||||
.video_opt
|
.video_opt
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -934,6 +963,7 @@ impl Application for App {
|
||||||
album_art_opt: None,
|
album_art_opt: None,
|
||||||
controls: true,
|
controls: true,
|
||||||
controls_time: Instant::now(),
|
controls_time: Instant::now(),
|
||||||
|
controls_hovered: false,
|
||||||
dropdown_opt: None,
|
dropdown_opt: None,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
key_binds: key_binds(),
|
key_binds: key_binds(),
|
||||||
|
|
@ -1671,6 +1701,18 @@ impl Application for App {
|
||||||
Message::ShowControls => {
|
Message::ShowControls => {
|
||||||
self.update_controls(true);
|
self.update_controls(true);
|
||||||
}
|
}
|
||||||
|
Message::ControlsHoverEnter => {
|
||||||
|
if !self.controls_hovered {
|
||||||
|
self.controls_hovered = true;
|
||||||
|
self.update_controls(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::ControlsHoverExit => {
|
||||||
|
if self.controls_hovered {
|
||||||
|
self.controls_hovered = false;
|
||||||
|
self.update_controls(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::SystemThemeModeChange(_theme_mode) => {
|
Message::SystemThemeModeChange(_theme_mode) => {
|
||||||
return self.update_config();
|
return self.update_config();
|
||||||
}
|
}
|
||||||
|
|
@ -2140,7 +2182,11 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !popup_items.is_empty() {
|
if !popup_items.is_empty() {
|
||||||
popover = popover.popup(widget::column::with_children(popup_items));
|
popover = popover.popup(
|
||||||
|
widget::mouse_area(widget::column::with_children(popup_items))
|
||||||
|
.on_enter(Message::ControlsHoverEnter)
|
||||||
|
.on_exit(Message::ControlsHoverExit),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
widget::container(popover)
|
widget::container(popover)
|
||||||
|
|
|
||||||
67
src/video.rs
67
src/video.rs
|
|
@ -4,6 +4,40 @@ use iced_video_player::{Video, gst, gst_app, gst_pbutils};
|
||||||
use cosmic::action;
|
use cosmic::action;
|
||||||
use cosmic::app::Task;
|
use cosmic::app::Task;
|
||||||
|
|
||||||
|
const SUSPICIOUS_AUDIO_GAP_DURATION: gst::ClockTime = gst::ClockTime::SECOND;
|
||||||
|
|
||||||
|
fn is_suspicious_audio_gap(duration: Option<gst::ClockTime>) -> bool {
|
||||||
|
duration.is_some_and(|duration| duration >= SUSPICIOUS_AUDIO_GAP_DURATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suppress_suspicious_audio_gaps(audio_filter: &gst::Element) {
|
||||||
|
let Some(src_pad) = audio_filter.static_pad("src") else {
|
||||||
|
log::warn!("audio filter has no source pad; malformed audio gaps will not be suppressed");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = src_pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, |_, info| {
|
||||||
|
let Some(event) = info.event() else {
|
||||||
|
return gst::PadProbeReturn::Ok;
|
||||||
|
};
|
||||||
|
let gst::EventView::Gap(gap) = event.view() else {
|
||||||
|
return gst::PadProbeReturn::Ok;
|
||||||
|
};
|
||||||
|
let (timestamp, duration) = gap.get();
|
||||||
|
if !is_suspicious_audio_gap(duration) {
|
||||||
|
return gst::PadProbeReturn::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some malformed MP4 edit lists cause streamsynchronizer to emit a
|
||||||
|
// long GAP before almost every audio buffer. The next buffer's PTS is
|
||||||
|
// still continuous, so forwarding those events makes audio sinks
|
||||||
|
// repeatedly wait or resynchronise and produces choppy playback.
|
||||||
|
log::debug!("suppressing audio GAP at {timestamp} with duration {duration:?}");
|
||||||
|
drop(info.take_event());
|
||||||
|
gst::PadProbeReturn::Handled
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct VideoSettings {
|
pub struct VideoSettings {
|
||||||
pub mute: bool,
|
pub mute: bool,
|
||||||
|
|
@ -33,11 +67,14 @@ pub fn new_video(
|
||||||
.downcast::<gst::Pipeline>()
|
.downcast::<gst::Pipeline>()
|
||||||
.map_err(|_| iced_video_player::Error::Cast)
|
.map_err(|_| iced_video_player::Error::Cast)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if let Ok(scaletempo) = gst::ElementFactory::make("scaletempo").build() {
|
let audio_filter = gst::ElementFactory::make("scaletempo")
|
||||||
pipeline.set_property("audio-filter", &scaletempo);
|
.build()
|
||||||
} else {
|
.unwrap_or_else(|_| {
|
||||||
log::warn!("scaletempo element not available; speed changes will affect pitch");
|
log::warn!("scaletempo element not available; speed changes will affect pitch");
|
||||||
}
|
gst::ElementFactory::make("identity").build().unwrap()
|
||||||
|
});
|
||||||
|
suppress_suspicious_audio_gaps(&audio_filter);
|
||||||
|
pipeline.set_property("audio-filter", &audio_filter);
|
||||||
pipeline.connect("element-setup", false, |vals| {
|
pipeline.connect("element-setup", false, |vals| {
|
||||||
let Ok(elem) = vals[1].get::<gst::Element>() else {
|
let Ok(elem) = vals[1].get::<gst::Element>() else {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -111,3 +148,25 @@ fn disable_broken_hardware_decoders() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::is_suspicious_audio_gap;
|
||||||
|
use iced_video_player::gst;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn long_audio_gap_is_suspicious() {
|
||||||
|
assert!(is_suspicious_audio_gap(Some(gst::ClockTime::SECOND)));
|
||||||
|
assert!(is_suspicious_audio_gap(Some(
|
||||||
|
gst::ClockTime::from_seconds(3)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn short_or_unknown_audio_gap_is_preserved() {
|
||||||
|
assert!(!is_suspicious_audio_gap(Some(
|
||||||
|
gst::ClockTime::from_mseconds(999)
|
||||||
|
)));
|
||||||
|
assert!(!is_suspicious_audio_gap(None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue