From 4b17ac7336e014e0bad540f15b9678447fd321d5 Mon Sep 17 00:00:00 2001 From: Martin Finkel Date: Mon, 31 Aug 2026 16:44:18 +0200 Subject: [PATCH] Fix track description traversal --- src/audio.rs | 11 ++---- src/tools.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/video.rs | 10 ++---- 3 files changed, 96 insertions(+), 24 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index c39891c..9d8d91f 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -5,7 +5,7 @@ use vlc_sys as sys; use crate::MediaPlayer; use crate::TrackDescription; -use crate::tools::from_cstr; +use crate::tools::track_descriptions_from_raw; pub trait MediaPlayerAudioEx { fn get_mute(&self) -> Option; @@ -44,16 +44,9 @@ impl MediaPlayerAudioEx for MediaPlayer { unsafe{ let p0 = sys::libvlc_audio_get_track_description(self.ptr); if p0.is_null() { return None; } - let mut td = Vec::new(); - let mut p = p0; - - while !(*p).p_next.is_null() { - td.push(TrackDescription{ id: (*p).i_id, name: from_cstr((*p).psz_name) }); - p = (*p).p_next; - } + let td = track_descriptions_from_raw(p0); sys::libvlc_track_description_list_release(p0); Some(td) } } - } diff --git a/src/tools.rs b/src/tools.rs index c44598a..39b2b46 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -2,10 +2,12 @@ // This file is part of vlc-rs. // Licensed under the MIT license, see the LICENSE file. -use std::ffi::{CString, CStr, NulError}; -use std::path::Path; -use std::borrow::Cow; +use crate::TrackDescription; use libc::c_char; +use std::borrow::Cow; +use std::ffi::{CStr, CString, NulError}; +use std::path::Path; +use vlc_sys as sys; // Convert String to CString. // Panic if the string includes null bytes. @@ -16,8 +18,8 @@ pub fn to_cstr(s: &str) -> CString { // Convert *const c_char to String pub unsafe fn from_cstr(p: *const c_char) -> Option { if p.is_null() { - None - }else{ + None + } else { let cstr = CStr::from_ptr(p); Some(cstr.to_string_lossy().into_owned()) @@ -27,8 +29,8 @@ pub unsafe fn from_cstr(p: *const c_char) -> Option { // Convert *const c_char to &str pub unsafe fn from_cstr_ref<'a>(p: *const c_char) -> Option> { if p.is_null() { - None - }else{ + None + } else { let cstr = CStr::from_ptr(p); Some(cstr.to_string_lossy()) @@ -41,3 +43,86 @@ pub fn path_to_cstr(path: &Path) -> Result { Ok(path) } + +/// Convert a libVLC track-description linked list into owned Rust values. +/// +/// The caller retains ownership of the list and must release it using +/// `libvlc_track_description_list_release` when appropriate. +pub unsafe fn track_descriptions_from_raw( + mut track: *mut sys::libvlc_track_description_t, +) -> Vec { + let mut descriptions = Vec::new(); + + while !track.is_null() { + descriptions.push(TrackDescription { + id: (*track).i_id, + name: from_cstr((*track).psz_name), + }); + track = (*track).p_next; + } + + descriptions +} + +#[cfg(test)] +mod tests { + use super::track_descriptions_from_raw; + use std::ffi::CString; + use std::ptr; + use vlc_sys::libvlc_track_description_t; + + fn track( + id: i32, + name: &CString, + next: *mut libvlc_track_description_t, + ) -> libvlc_track_description_t { + libvlc_track_description_t { + i_id: id, + psz_name: name.as_ptr() as *mut _, + p_next: next, + } + } + + #[test] + fn track_descriptions_from_raw_handles_a_null_list() { + let descriptions = unsafe { track_descriptions_from_raw(ptr::null_mut()) }; + + assert!(descriptions.is_empty()); + } + + #[test] + fn track_descriptions_from_raw_includes_a_singleton() { + let name = CString::new("English").unwrap(); + let mut node = track(1, &name, ptr::null_mut()); + + let descriptions = unsafe { track_descriptions_from_raw(&mut node) }; + + assert_eq!(descriptions.len(), 1); + assert_eq!(descriptions[0].id, 1); + assert_eq!(descriptions[0].name.as_deref(), Some("English")); + } + + #[test] + fn track_descriptions_from_raw_includes_the_final_node() { + let first_name = CString::new("English").unwrap(); + let second_name = CString::new("French").unwrap(); + let mut second = track(2, &second_name, ptr::null_mut()); + let mut first = track(1, &first_name, &mut second); + + let descriptions = unsafe { track_descriptions_from_raw(&mut first) }; + + assert_eq!( + descriptions, + vec![ + crate::TrackDescription { + id: 1, + name: Some("English".to_owned()), + }, + crate::TrackDescription { + id: 2, + name: Some("French".to_owned()), + }, + ] + ); + } +} diff --git a/src/video.rs b/src/video.rs index e9ca4fc..3a242d7 100644 --- a/src/video.rs +++ b/src/video.rs @@ -6,7 +6,7 @@ use vlc_sys as sys; use crate::MediaPlayer; use crate::TrackDescription; use crate::enums::VideoAdjustOption; -use crate::tools::{to_cstr, from_cstr}; +use crate::tools::{to_cstr, from_cstr, track_descriptions_from_raw}; use libc::c_void; pub trait MediaPlayerVideoEx { @@ -98,13 +98,7 @@ impl MediaPlayerVideoEx for MediaPlayer { unsafe{ let p0 = sys::libvlc_video_get_track_description(self.ptr); if p0.is_null() { return None; } - let mut td = Vec::new(); - let mut p = p0; - - while !(*p).p_next.is_null() { - td.push(TrackDescription{ id: (*p).i_id, name: from_cstr((*p).psz_name) }); - p = (*p).p_next; - } + let td = track_descriptions_from_raw(p0); sys::libvlc_track_description_list_release(p0); Some(td) }