Как получить иллюстрацию из потокового мультимедиа в формате MP3?

Я использую 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 (если не считать ее записи во временный файл и его загрузки)?

IIRC, когда входными данными является поток, анализатор VlcLib всегда устанавливает _SKIPPED (или любой другой флаг состояния, который есть в исходном коде C), настраивая обратные вызовы предварительного анализатора ввода. Таким образом, даже если вы можете установить объект Media для MediaPlayer и подписаться на событие ParsedChanged, даже когда ParsedStatus == MediaParsedStatus.Done (который, IIRC2, должен быть таким после MediaPlayer.Play()), у вас все равно установлен статус Пропущено.

Jimi 27.08.2024 19:20

@jimi Спасибо! Я только что протестировал ParsedChanged и ParsedStatus. Ваша память верна. ParsedStatus возвращается «Пропущено» для потоков и «Готово» для файлов. Преобразуйте ваш комментарий в ответ, и я приму его.

d ei 28.08.2024 15:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

После просмотра исходного кода репозитория 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? Я удаляю все эти объекты, но исключил обработку удаления из опубликованного кода, чтобы сделать его коротким. Еще раз спасибо.

d ei 29.08.2024 15:27

Да, вам действительно нужно. Нет проблем, если вы не используете StreamMediaInput, вы можете добавить media.Dispose(); сразу после mediaPlayer.Play();, но когда StreamMediaInput задействован, вы действительно не можете этого сделать. Вам следует сохранить оба поля как поле и удалить оба, прежде чем снова начать потоковую передачу. Тогда у вас может возникнуть потенциальная проблема с параллелизмом, если вы передаете более одного ввода одновременно. Вам нужна коллекция этих объектов. Вот почему я написал, что необходимо дальнейшее тестирование в этом отделе, поскольку функциональность MediaPlayer работает по принципу «выстрелил и забыл». все равно добавлю заметку

Jimi 29.08.2024 15:57

Другие вопросы по теме