Я использую libVLCSharp для воспроизведения аудио и видео в приложении Winforms. Я могу получить встроенную обложку из файлов MP3, вызвав этот метод PlayMediaFile() с FileInfo, указывающим на MP3 с обложкой:`
// media & artwork must be disposed after playing media
async Task PlayMediaFile(FileInfo mediaFI) {
var media = new Media(LibVlc, mediaFI.FullName, FromType.FromPath);
await media.Parse();
Play(media);
}
void Play(Media media) {
var artworkPath = media.Meta(MetadataType.ArtworkURL);
Bitmap? artwork = null;
if (artworkPath is not null) {
var artworkUri = new Uri(artworkPath);
var artworkFI = new FileInfo(artworkUri.LocalPath);
artwork = artworkFI.Exists ? new Bitmap(artworkFI.FullName) : null;
}
MediaPlayer.Media = media;
videoView.BackgroundImage = artwork;
MediaPlayer.Play();
}
`
Однако, если я попытаюсь воспроизвести поток MP3, используя:`
// stream, media, mediaInput & artwork must be disposed after playing media
async Task PlayMediaStream(Stream stream) {
var mediaInput = new StreamMediaInput(stream);
var media = new Media(LibVlc, mediaInput);
await media.Parse(MediaParseOptions.ParseLocal); // and combinations with MediaParseOptions.ParseNetwork, MediaParseOptions.FetchNetwork - none worked
Play(media);
}
` Объект Artwork всегда имеет значение null. Я знаю, что в потоке есть иллюстрации; Я могу сохранить его в файл и получить с помощью метода PlayMediaFile(). Есть ли какой-нибудь способ получить иллюстрацию из медиапотока MP3 (если не считать ее записи во временный файл и его загрузки)?
@jimi Спасибо! Я только что протестировал ParsedChanged
и ParsedStatus
. Ваша память верна. ParsedStatus
возвращается «Пропущено» для потоков и «Готово» для файлов. Преобразуйте ваш комментарий в ответ, и я приму его.
После просмотра исходного кода репозитория VideoLan VLC , а точнее файла preparser.c, выяснилось, что такое поведение является намеренным.
Когда препарсер готовит задачи для обратных вызовов (см. функцию vlc_preparser_Push(), оцениваются параметры (сопоставленные с MediaParseOptions
).
Если текущий элемент мультимедиа не является локальным файлом, каталогом или списком воспроизведения, то синтаксический анализ пропускается (устанавливается флаг ITEM_PREPARSE_SKIPPED
) и немедленно возвращает успех.
Вероятно, это связано с выбором реализации, которая считает быструю реакцию на запрос потоковой передачи более важной, чем буферизация, достаточная для доступа к разделу, где хранятся метаданные.
Конечно, это происходит потому, что абстрактный класс MediaInput
и производный класс StreamMediaInput
ведут себя следующим образом.
Ничто не мешает вам создать собственный обработчик потока из MediaInput
и предоставить альтернативное поведение буферизации, отслеживая текущую позицию чтения и осуществляя дальнейшую буферизацию для завершения потока и получения метаданных до завершения воспроизведения.
Затем проанализируйте метаданные и уведомите, например, с помощью событий, что анализ завершен.
Легкая формулировка, но на практике не все так просто. Требует большого количества испытаний.
В любом случае я протестировал все комбинации MediaParseOptions
, чтобы посмотреть, что произойдет (потому что это реализация LibVLCSharp), но ParsedStatus
всегда MediaParsedStatus.Skipped
, как и предполагает исходный код.
Во время тестирования я немного изменил код, чтобы он был более совместим с IDisposable. Необходимо дальнейшее тестирование в этом отделе, поскольку функциональность MediaPlayer проста в использовании.
Точнее, объекты StreamMediaInput
и Media
должны быть удалены перед воспроизведением нового потока. Текущая реализация этого не делает (также нет финализаторов)
Image? Artwork { get; set; }
LibVLC libVlc = new LibVLC();
MediaPlayer? mediaPlayer = null;
async Task PlayMediaStream(Stream stream) {
mediaPlayer?.Dispose();
mediaPlayer = new MediaPlayer(libVlc);
Artwork?.Dispose();
Artwork = null;
var mediaInput = new StreamMediaInput(stream);
var media = new Media(libVlc, mediaInput);
media.ParsedChanged += (s, a) => {
if (a.ParsedStatus == MediaParsedStatus.Done) {
var artworkPath = media.Meta(MetadataType.ArtworkURL);
if (artworkPath is not null) {
var artworkFI = new FileInfo(new Uri(artworkPath).LocalPath);
if (artworkFI.Exists) {
Artwork = new Bitmap(artworkFI.FullName);
}
}
}
videoView.BackgroundImage = Artwork;
};
// Whatever combination
await media.Parse(MediaParseOptions.FetchLocal | MediaParseOptions.ParseLocal |
MediaParseOptions.FetchNetwork | MediaParseOptions.ParseNetwork);
mediaPlayer.Media = media;
var success = mediaPlayer.Play();
}
Разве не нужно также удалять mediaInput и media? Я удаляю все эти объекты, но исключил обработку удаления из опубликованного кода, чтобы сделать его коротким. Еще раз спасибо.
Да, вам действительно нужно. Нет проблем, если вы не используете StreamMediaInput, вы можете добавить media.Dispose();
сразу после mediaPlayer.Play();
, но когда StreamMediaInput задействован, вы действительно не можете этого сделать. Вам следует сохранить оба поля как поле и удалить оба, прежде чем снова начать потоковую передачу. Тогда у вас может возникнуть потенциальная проблема с параллелизмом, если вы передаете более одного ввода одновременно. Вам нужна коллекция этих объектов. Вот почему я написал, что необходимо дальнейшее тестирование в этом отделе, поскольку функциональность MediaPlayer работает по принципу «выстрелил и забыл». все равно добавлю заметку
IIRC, когда входными данными является поток, анализатор VlcLib всегда устанавливает
_SKIPPED
(или любой другой флаг состояния, который есть в исходном коде C), настраивая обратные вызовы предварительного анализатора ввода. Таким образом, даже если вы можете установить объект Media для MediaPlayer и подписаться на событиеParsedChanged
, даже когдаParsedStatus == MediaParsedStatus.Done
(который, IIRC2, должен быть таким послеMediaPlayer.Play()
), у вас все равно установлен статус Пропущено.