Compare commits
14 commits
post-1.6-m
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ee781bfc2 | |||
| cfdeaa3ae1 | |||
|
|
66b3f34335 | ||
|
|
3d8af19ac4 | ||
| 78b806a347 | |||
| 45c9b1aa19 | |||
|
|
a297c24a96 | ||
|
|
f0365d411b | ||
|
|
e040f7fbc7 | ||
| 89dd5e1f10 | |||
| 3171217e5a | |||
|
|
c9cba75c84 | ||
|
|
23254703eb | ||
|
|
b5f2fffe65 |
11 changed files with 325 additions and 341 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -10,4 +10,4 @@
|
|||
!/vendor/iced_video_player/
|
||||
!/vendor/iced_video_player/**
|
||||
/vendor.tar
|
||||
.idea/
|
||||
.idea/
|
||||
516
Cargo.lock
generated
516
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cosmic-player"
|
||||
version = "1.6.0"
|
||||
version = "1.8.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.93"
|
||||
|
||||
|
|
|
|||
12
debian/changelog
vendored
12
debian/changelog
vendored
|
|
@ -1,3 +1,15 @@
|
|||
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
|
||||
|
||||
* Epoch 1.6.0 version update
|
||||
|
|
|
|||
|
|
@ -9,3 +9,28 @@ settings = Indstillinger
|
|||
open-file = Åbn fil
|
||||
open-folder = Åbn mappe
|
||||
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;
|
||||
|
|
|
|||
0
i18n/fil/cosmic_player.ftl
Normal file
0
i18n/fil/cosmic_player.ftl
Normal file
|
|
@ -2,13 +2,13 @@ open-file = Ldi afaylu
|
|||
open-folder = Ldi akaram
|
||||
repeat-disabled = Allus yensa
|
||||
repeat-track = Ales tazribt
|
||||
quit = Tuffɣa
|
||||
quit = Ffeɣ
|
||||
settings = Iɣewwaṛen
|
||||
appearance = Timeẓri
|
||||
theme = Asentel
|
||||
dark = Aɣmayan
|
||||
light = Aceɛlal
|
||||
match-desktop = Amṣada d tnarit
|
||||
match-desktop = Semṣada akked tnarit
|
||||
file = Afaylu
|
||||
close-file = Mdel afaylu
|
||||
album = Album: { $album }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
settings = Paramètres
|
||||
settings = Configuracion
|
||||
album = Album : { $album }
|
||||
audio = Àudio
|
||||
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
|
||||
theme = Tèma
|
||||
match-desktop = Assortir al burèu
|
||||
dark = Fosc
|
||||
dark = Escur
|
||||
light = Clar
|
||||
file = Fichièr
|
||||
open-media = Dorbir mèdia...
|
||||
|
|
@ -31,3 +31,4 @@ ab-repeat-clear = Escafar la repeticion A-B
|
|||
xdg-name = Lector multimèdia COSMIC
|
||||
xdg-comment = Lector multimèdia pel burèu COSMIC
|
||||
xdg-keywords = Àudio;Musica;Vidèo;Film;Son;
|
||||
quit = Quitar
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
settings = การตั้งค่า
|
||||
dark = มืด
|
||||
light = สว่าง
|
||||
appearance = ลักษณะ
|
||||
27
src/main.rs
27
src/main.rs
|
|
@ -82,7 +82,32 @@ fn get_framerate(video: &Video) -> Option<f64> {
|
|||
let caps = pad.current_caps()?;
|
||||
let structure = caps.structure(0)?;
|
||||
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
|
||||
|
|
|
|||
69
src/video.rs
69
src/video.rs
|
|
@ -4,6 +4,40 @@ use iced_video_player::{Video, gst, gst_app, gst_pbutils};
|
|||
use cosmic::action;
|
||||
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)]
|
||||
pub struct VideoSettings {
|
||||
pub mute: bool,
|
||||
|
|
@ -33,11 +67,14 @@ pub fn new_video(
|
|||
.downcast::<gst::Pipeline>()
|
||||
.map_err(|_| iced_video_player::Error::Cast)
|
||||
.unwrap();
|
||||
if let Ok(scaletempo) = gst::ElementFactory::make("scaletempo").build() {
|
||||
pipeline.set_property("audio-filter", &scaletempo);
|
||||
} else {
|
||||
log::warn!("scaletempo element not available; speed changes will affect pitch");
|
||||
}
|
||||
let audio_filter = gst::ElementFactory::make("scaletempo")
|
||||
.build()
|
||||
.unwrap_or_else(|_| {
|
||||
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| {
|
||||
let Ok(elem) = vals[1].get::<gst::Element>() else {
|
||||
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