6#include <common/mfmetadata_p.h>
8#include <player/qmediaenginenotify_p.h>
9#include <player/qmediaenginetimedtextnotify_p.h>
10#include <player/qmediaenginevideobuffer_p.h>
12#include <QtMultimedia/qaudiooutput.h>
13#include <QtMultimedia/qvideosink.h>
14#include <QtMultimedia/private/qcomtaskresource_p.h>
15#include <QtMultimedia/private/qplatformaudiooutput_p.h>
16#include <QtMultimedia/private/qplatformvideosink_p.h>
17#include <QtMultimedia/private/qvideoframe_p.h>
18#include <QtMultimedia/private/qwindows_scopedpropvariant_p.h>
19#include <QtMultimedia/private/qwindowsd3d11rhitextureimporter_p.h>
20#include <QtMultimedia/private/qwmf_support_p.h>
21#include <QtConcurrent/qtconcurrentrun.h>
22#include <QtCore/qfile.h>
23#include <QtCore/qlocale.h>
24#include <QtCore/qloggingcategory.h>
25#include <QtCore/qscopeguard.h>
26#include <QtCore/qtimer.h>
27#include <QtCore/private/qbstr_p.h>
28#include <QtCore/private/qsystemerror_p.h>
38using namespace Qt::StringLiterals;
39using namespace std::chrono_literals;
41using reference_time = QWMF::reference_time;
45QVariant languageVariant(
const QString &langStr)
47 const QLocale::Language lang = QLocale::codeToLanguage(langStr, QLocale::AnyLanguageCode);
48 return lang != QLocale::AnyLanguage ? QVariant::fromValue(lang) : QVariant(langStr);
51QMediaPlayer::Error mapMediaEngineError(MF_MEDIA_ENGINE_ERR err)
54 case MF_MEDIA_ENGINE_ERR_NOERROR:
55 return QMediaPlayer::NoError;
56 case MF_MEDIA_ENGINE_ERR_ABORTED:
57 return QMediaPlayer::ResourceError;
58 case MF_MEDIA_ENGINE_ERR_NETWORK:
59 return QMediaPlayer::NetworkError;
60 case MF_MEDIA_ENGINE_ERR_DECODE:
61 return QMediaPlayer::FormatError;
62 case MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED:
63 return QMediaPlayer::ResourceError;
64 case MF_MEDIA_ENGINE_ERR_ENCRYPTED:
65 return QMediaPlayer::AccessDeniedError;
67 return QMediaPlayer::ResourceError;
71std::chrono::microseconds videoFrameDurationUs(IMFMediaEngineEx *engine)
76 DWORD streamCount = 0;
77 if (FAILED(engine->GetNumberOfStreams(&streamCount)))
80 for (DWORD i = 0; i < streamCount; ++i) {
81 ScopedPropVariant majorType;
82 if (FAILED(engine->GetStreamAttribute(i, MF_MT_MAJOR_TYPE, majorType.get()))
83 || majorType->vt != VT_CLSID || *majorType->puuid != MFMediaType_Video) {
87 ScopedPropVariant rate;
88 if (FAILED(engine->GetStreamAttribute(i, MF_MT_FRAME_RATE, rate.get()))
89 || rate->vt != VT_UI8) {
93 UINT64 avgTimePerFrame = 0;
94 if (SUCCEEDED(MFFrameRateToAverageTimePerFrame(
95 HI32(rate->uhVal.QuadPart), LO32(rate->uhVal.QuadPart), &avgTimePerFrame)))
96 return std::chrono::round<
std::chrono::microseconds>(reference_time(avgTimePerFrame));
107 m_textureImporter = std::make_shared<QtMultimediaPrivate::QWindowsD3D11RhiTextureImporter>();
109 const D3D_FEATURE_LEVEL featureLevels[] = {
110 D3D_FEATURE_LEVEL_11_1,
111 D3D_FEATURE_LEVEL_11_0,
114 UINT flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT | D3D11_CREATE_DEVICE_BGRA_SUPPORT;
116 HRESULT hr = D3D11CreateDevice(
nullptr, D3D_DRIVER_TYPE_HARDWARE,
nullptr, flags, featureLevels,
117 ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &m_d3dDevice,
121 qCWarning(lcMediaEngine) <<
"D3D11CreateDevice:" << QSystemError::windowsComString(hr);
125 ComPtr<ID3D10Multithread> multithread;
126 if (SUCCEEDED(m_d3dDevice.As(&multithread)))
127 multithread->SetMultithreadProtected(TRUE);
134 HRESULT hr = MFCreateDXGIDeviceManager(&m_resetToken, &m_dxgiManager);
136 qCWarning(lcMediaEngine) <<
"MFCreateDXGIDeviceManager:"
137 << QSystemError::windowsComString(hr);
141 hr = m_dxgiManager->ResetDevice(m_d3dDevice.Get(), m_resetToken);
143 qCWarning(lcMediaEngine) <<
"ResetDevice:" << QSystemError::windowsComString(hr);
147 m_notify = makeComObject<QMediaEngineNotify>();
150 QObject::connect(m_notify.Get(), &QMediaEngineNotify::eventNotified, context(),
151 [
this](DWORD event, DWORD_PTR param1, DWORD param2) {
152 handleMediaEngineEvent(event, param1, param2);
153 }, Qt::QueuedConnection);
155 ComPtr<IMFAttributes> attrs;
156 hr = MFCreateAttributes(&attrs, 4);
158 qCWarning(lcMediaEngine) <<
"MFCreateAttributes:" << QSystemError::windowsComString(hr);
162 attrs->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, m_notify.Get());
163 attrs->SetUnknown(MF_MEDIA_ENGINE_DXGI_MANAGER, m_dxgiManager.Get());
164 attrs->SetUINT32(MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM);
166 ComPtr<IMFMediaEngineClassFactory> factory;
167 hr = CoCreateInstance(CLSID_MFMediaEngineClassFactory,
nullptr, CLSCTX_INPROC_SERVER,
168 IID_PPV_ARGS(&factory));
170 qCWarning(lcMediaEngine) <<
"CoCreateInstance(MFMediaEngineClassFactory):"
171 << QSystemError::windowsComString(hr);
175 hr = factory->CreateInstance(0, attrs.Get(), &m_mediaEngine);
177 qCWarning(lcMediaEngine) <<
"IMFMediaEngine::CreateInstance:"
178 << QSystemError::windowsComString(hr);
182 hr = m_mediaEngine.As(&m_mediaEngineEx);
184 qCWarning(lcMediaEngine) <<
"IMFMediaEngineEx:" << QSystemError::windowsComString(hr);
190 ComPtr<IMFGetService> getService;
191 if (SUCCEEDED(m_mediaEngine.As(&getService))) {
192 getService->GetService(MF_MEDIA_ENGINE_TIMEDTEXT, IID_PPV_ARGS(&m_timedText));
196 m_timedText->SetInBandEnabled(TRUE);
198 m_timedTextNotify = makeComObject<QMediaEngineTimedTextNotify>();
200 QObject::connect(m_timedTextNotify.Get(), &QMediaEngineTimedTextNotify::trackAdded,
202 updateSubtitleTracks();
205 QObject::connect(m_timedTextNotify.Get(), &QMediaEngineTimedTextNotify::subtitleTextChanged,
206 context(), [
this](
const QString &text) {
208 m_videoSink->setSubtitleText(text);
211 m_timedText->RegisterNotifications(m_timedTextNotify.Get());
215 if (!qFuzzyCompare(m_playbackRate, 1.0)) {
216 m_mediaEngine->SetDefaultPlaybackRate(m_playbackRate);
217 m_mediaEngine->SetPlaybackRate(m_playbackRate);
226 m_mediaEngine->Shutdown();
227 m_mediaEngine.Reset();
229 m_mediaEngineEx.Reset();
231 m_timedTextNotify.Reset();
233 m_dxgiManager.Reset();
239 m_positionUpdateTimer.start();
240 m_videoRenderTimer.start();
245 m_positionUpdateTimer.stop();
246 m_videoRenderTimer.stop();
252 error(QMediaPlayer::ResourceError, u"Failed to initialize D3D11"_s);
254 m_positionUpdateTimer.callOnTimeout(context(), [
this] {
255 if (m_mediaEngine && !m_seekTarget)
256 positionChanged(currentPositionMs());
259 m_videoRenderTimer.callOnTimeout(context(), [
this] {
263 m_seekTimeoutTimer.setSingleShot(
true);
264 m_seekTimeoutTimer.callOnTimeout(context(), [
this] {
266 qCWarning(lcMediaEngine) <<
"Seek timeout — SEEKED event not received, resetting";
267 m_seekTarget.reset();
274 stopPlaybackTimers();
275 m_seekTimeoutTimer.stop();
277 m_metadataFuture.cancel();
279 releaseMediaEngine();
282void QMediaEnginePlayer::handleMediaEngineEvent(DWORD event, DWORD_PTR param1, DWORD param2)
290 case MF_MEDIA_ENGINE_EVENT_LOADEDMETADATA:
291 qCDebug(lcMediaEngine) <<
"LOADEDMETADATA";
297 case MF_MEDIA_ENGINE_EVENT_LOADEDDATA: {
298 qCDebug(lcMediaEngine) <<
"LOADEDDATA";
299 audioAvailableChanged(m_mediaEngine->HasAudio());
300 videoAvailableChanged(m_mediaEngine->HasVideo());
301 updateVideoFrameFormat();
303 seekableChanged(!m_stream || !m_stream->isSequential());
304 mediaStatusChanged(QMediaPlayer::LoadedMedia);
307 bufferProgressChanged(bp);
310 m_pendingPlay =
false;
316 case MF_MEDIA_ENGINE_EVENT_CANPLAY:
317 qCDebug(lcMediaEngine) <<
"CANPLAY";
318 if (state() == QMediaPlayer::PlayingState || mediaStatus() == QMediaPlayer::BufferingMedia
320 mediaStatusChanged(QMediaPlayer::BufferedMedia);
322 bufferProgressChanged(1.0f);
325 case MF_MEDIA_ENGINE_EVENT_PLAYING:
326 qCDebug(lcMediaEngine) <<
"PLAYING";
327 if (state() != QMediaPlayer::PlayingState)
328 stateChanged(QMediaPlayer::PlayingState);
329 if (mediaStatus() == QMediaPlayer::BufferingMedia)
330 mediaStatusChanged(QMediaPlayer::BufferedMedia);
331 startPlaybackTimers();
334 case MF_MEDIA_ENGINE_EVENT_PAUSE:
335 qCDebug(lcMediaEngine) <<
"PAUSE";
336 if (state() != QMediaPlayer::StoppedState && state() != QMediaPlayer::PausedState) {
337 stateChanged(QMediaPlayer::PausedState);
338 stopPlaybackTimers();
342 case MF_MEDIA_ENGINE_EVENT_ENDED: {
343 qCDebug(lcMediaEngine) <<
"ENDED";
344 stopPlaybackTimers();
346 if (mediaStatus() == QMediaPlayer::BufferingMedia)
347 mediaStatusChanged(QMediaPlayer::BufferedMedia);
350 m_mediaEngine->SetCurrentTime(0);
351 m_mediaEngine->Play();
354 m_videoSink->setVideoFrame({});
355 positionChanged(m_duration.count());
356 stateChanged(QMediaPlayer::StoppedState);
357 mediaStatusChanged(QMediaPlayer::EndOfMedia);
363 case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE:
365 positionChanged(currentPositionMs());
368 case MF_MEDIA_ENGINE_EVENT_ERROR: {
369 const auto mfErr =
static_cast<MF_MEDIA_ENGINE_ERR>(param1);
373 case MF_MEDIA_ENGINE_ERR_ABORTED:
374 detail = u"Playback aborted"_s;
376 case MF_MEDIA_ENGINE_ERR_NETWORK:
377 detail = u"Network error"_s;
379 case MF_MEDIA_ENGINE_ERR_DECODE:
380 detail = u"Unsupported or corrupt media"_s;
382 case MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED:
383 detail = u"Source not supported"_s;
385 case MF_MEDIA_ENGINE_ERR_ENCRYPTED:
386 detail = u"Encrypted content"_s;
389 detail = u"Unknown error %1"_s.arg(param1);
393 qCWarning(lcMediaEngine) <<
"Error:" << detail;
394 error(mapMediaEngineError(mfErr), detail);
395 mediaStatusChanged(QMediaPlayer::InvalidMedia);
396 stopPlaybackTimers();
400 case MF_MEDIA_ENGINE_EVENT_BUFFERINGSTARTED:
401 qCDebug(lcMediaEngine) <<
"BUFFERINGSTARTED";
402 mediaStatusChanged(QMediaPlayer::BufferingMedia);
406 case MF_MEDIA_ENGINE_EVENT_BUFFERINGENDED:
407 qCDebug(lcMediaEngine) <<
"BUFFERINGENDED";
408 mediaStatusChanged(QMediaPlayer::BufferedMedia);
412 case MF_MEDIA_ENGINE_EVENT_DURATIONCHANGE:
413 qCDebug(lcMediaEngine) <<
"DURATIONCHANGE";
417 case MF_MEDIA_ENGINE_EVENT_FORMATCHANGE:
418 qCDebug(lcMediaEngine) <<
"FORMATCHANGE";
419 updateVideoFrameFormat();
422 case MF_MEDIA_ENGINE_EVENT_FIRSTFRAMEREADY:
423 qCDebug(lcMediaEngine) <<
"FIRSTFRAMEREADY";
424 if (state() != QMediaPlayer::StoppedState)
428 case MF_MEDIA_ENGINE_EVENT_SEEKED:
429 qCDebug(lcMediaEngine) <<
"SEEKED";
430 m_seekTimeoutTimer.stop();
431 m_seekTarget.reset();
441 qCDebug(lcMediaEngine) <<
"setMedia:" << media <<
"stream:" << stream;
443 if (state() != QMediaPlayer::StoppedState)
450 m_pendingPlay =
false;
451 m_seekTarget.reset();
452 m_seekTimeoutTimer.stop();
453 seekableChanged(
false);
455 m_transferTexture.Reset();
457 m_metadataFuture.cancel();
460 m_videoSink->setVideoFrame({});
464 if (media.isEmpty() && !stream) {
465 audioAvailableChanged(
false);
466 videoAvailableChanged(
false);
470 releaseMediaEngine();
472 mediaStatusChanged(QMediaPlayer::NoMedia);
478 if (!m_mediaEngine) {
479 if (!initMediaEngine()) {
480 error(QMediaPlayer::ResourceError, u"MediaEngine not initialized"_s);
481 mediaStatusChanged(QMediaPlayer::InvalidMedia);
489 ComPtr<MFStream> mfStream = makeComObject<MFStream>(stream,
false);
490 QString urlStr = media.toString();
494 if (urlStr.isEmpty()) {
495 if (
auto *file = qobject_cast<QFile *>(stream))
496 urlStr = u"file:///"_s + file->fileName();
498 urlStr = u"stream:///media"_s;
500 const QBStr bstrUrl(urlStr);
501 hr = m_mediaEngineEx->SetSourceFromByteStream(mfStream.Get(), bstrUrl.bstr());
503 const QBStr bstrUrl(media.toString());
504 hr = m_mediaEngine->SetSource(bstrUrl.bstr());
508 qCWarning(lcMediaEngine) <<
"SetSource:" << QSystemError::windowsComString(hr);
509 error(QMediaPlayer::ResourceError, u"Failed to set media source"_s);
510 mediaStatusChanged(QMediaPlayer::InvalidMedia);
514 mediaStatusChanged(QMediaPlayer::LoadingMedia);
523 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia) {
527 if (mediaStatus() == QMediaPlayer::LoadingMedia) {
528 m_pendingPlay =
true;
532 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
534 m_mediaEngine->SetCurrentTime(0);
538 mediaStatusChanged(QMediaPlayer::BufferingMedia);
539 }
else if (mediaStatus() == QMediaPlayer::LoadedMedia
540 || mediaStatus() == QMediaPlayer::BufferedMedia) {
541 mediaStatusChanged(QMediaPlayer::BufferingMedia);
544 HRESULT hr = m_mediaEngine->Play();
546 qCWarning(lcMediaEngine) <<
"Play:" << QSystemError::windowsComString(hr);
550 stateChanged(QMediaPlayer::PlayingState);
551 startPlaybackTimers();
559 m_pendingPlay =
false;
560 HRESULT hr = m_mediaEngine->Pause();
562 qCWarning(lcMediaEngine) <<
"Pause:" << QSystemError::windowsComString(hr);
566 if (mediaStatus() != QMediaPlayer::NoMedia && mediaStatus() != QMediaPlayer::InvalidMedia) {
567 stateChanged(QMediaPlayer::PausedState);
568 m_positionUpdateTimer.stop();
572 if (isVideoAvailable() && !m_videoRenderTimer.isActive())
573 m_videoRenderTimer.start();
581 m_pendingPlay =
false;
582 m_seekTarget.reset();
583 m_seekTimeoutTimer.stop();
584 stopPlaybackTimers();
589 if (mediaStatus() != QMediaPlayer::EndOfMedia) {
590 HRESULT hr = m_mediaEngine->Pause();
592 qCWarning(lcMediaEngine) <<
"Pause (in stop):" << QSystemError::windowsComString(hr);
597 positionChanged(currentPositionMs());
599 m_mediaEngine->SetCurrentTime(0);
602 m_videoSink->setVideoFrame({});
604 stateChanged(QMediaPlayer::StoppedState);
608 if (mediaStatus() == QMediaPlayer::BufferedMedia
609 || mediaStatus() == QMediaPlayer::BufferingMedia
610 || mediaStatus() == QMediaPlayer::EndOfMedia) {
611 mediaStatusChanged(QMediaPlayer::LoadedMedia);
612 bufferProgressChanged(0);
623 if (m_seekTarget.has_value())
624 return m_seekTarget->count();
626 return currentPositionMs();
633 const qint64 pos = qRound64(m_mediaEngine->GetCurrentTime() * 1000.0);
635 return m_duration.count() > 0 ? qMin(pos, m_duration.count()) : pos;
643 m_mediaEngine->GetNativeVideoSize(&w, &h);
644 return {
static_cast<
int>(w),
static_cast<
int>(h) };
652 position = qMax(qint64(0), position);
653 if (
const qint64 dur = duration(); dur > 0)
654 position = qMin(position, dur);
660 const bool wasEnded = m_mediaEngine->IsEnded();
662 m_seekTarget = std::chrono::milliseconds(position);
663 m_mediaEngine->SetCurrentTime(position / 1000.0);
665 if (wasEnded && state() != QMediaPlayer::PlayingState)
666 m_mediaEngine->Pause();
670 m_seekTimeoutTimer.start();
672 if (mediaStatus() == QMediaPlayer::EndOfMedia)
673 mediaStatusChanged(QMediaPlayer::LoadedMedia);
675 positionChanged(position);
677 if (isVideoAvailable() && state() == QMediaPlayer::PausedState)
678 m_videoRenderTimer.start();
683 return m_playbackRate;
688 if (qFuzzyCompare(rate, m_playbackRate))
691 m_playbackRate = rate;
694 m_mediaEngine->SetDefaultPlaybackRate(rate);
695 m_mediaEngine->SetPlaybackRate(rate);
698 playbackRateChanged(rate);
706 ComPtr<IMFMediaTimeRange> buffered;
707 if (FAILED(m_mediaEngine->GetBuffered(&buffered)) || !buffered)
710 const DWORD count = buffered->GetLength();
714 const double dur = m_mediaEngine->GetDuration();
715 if (!std::isfinite(dur) || dur <= 0)
719 buffered->GetEnd(count - 1, &end);
720 return static_cast<
float>(qBound(0.0, end / dur, 1.0));
725 QMediaTimeRange ranges;
729 ComPtr<IMFMediaTimeRange> buffered;
730 if (FAILED(m_mediaEngine->GetBuffered(&buffered)) || !buffered)
733 const DWORD count = buffered->GetLength();
735 for (DWORD i = 0; i < count; ++i) {
736 double start = 0, end = 0;
737 buffered->GetStart(i, &start);
738 buffered->GetEnd(i, &end);
739 ranges.addInterval(qRound64(start * 1000), qRound64(end * 1000));
747 return m_duration.count();
763 return PitchCompensationAvailability::AlwaysOn;
773 if (!m_mediaEngine || !isVideoAvailable())
779 const bool isPlaying = (state() == QMediaPlayer::PlayingState);
780 auto stopGuard = qScopeGuard([&] {
781 if (!isPlaying && !m_seekTarget)
782 m_videoRenderTimer.stop();
786 if (m_mediaEngine->OnVideoStreamTick(&pts) != S_OK)
797 const QSize videoSize = nativeVideoSize();
798 if (videoSize.isEmpty())
801 if (videoSize != m_videoSize) {
802 m_videoSize = videoSize;
803 m_transferTexture.Reset();
806 if (!m_transferTexture) {
807 D3D11_TEXTURE2D_DESC desc = {};
808 desc.Width = videoSize.width();
809 desc.Height = videoSize.height();
812 desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
813 desc.SampleDesc.Count = 1;
814 desc.Usage = D3D11_USAGE_DEFAULT;
815 desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
818 HRESULT hr = m_d3dDevice->CreateTexture2D(&desc,
nullptr, &m_transferTexture);
820 qCWarning(lcMediaEngine) <<
"CreateTexture2D:" << QSystemError::windowsComString(hr);
824 updateVideoFrameFormat();
827 MFVideoNormalizedRect srcRect = { 0.0f, 0.0f, 1.0f, 1.0f };
828 RECT destRect = { 0, 0, videoSize.width(), videoSize.height() };
830 HRESULT hr = m_mediaEngine->TransferVideoFrame(m_transferTexture.Get(), &srcRect, &destRect,
834 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
836 if (m_deviceLostRetries >= kMaxDeviceLostRetries) {
837 qCWarning(lcMediaEngine) <<
"Device lost recovery failed after"
838 << m_deviceLostRetries <<
"attempts, giving up";
839 error(QMediaPlayer::ResourceError, u"D3D11 device lost recovery failed"_s);
842 ++m_deviceLostRetries;
844 qCWarning(lcMediaEngine) <<
"Device lost during TransferVideoFrame, recovering"
845 <<
"(attempt" << m_deviceLostRetries <<
")...";
847 const qint64 savedPosition = currentPositionMs();
848 const auto savedState = state();
849 stopPlaybackTimers();
852 releaseMediaEngine();
853 m_transferTexture.Reset();
857 QTimer::singleShot(0, context(), [
this, savedPosition, savedState] {
859 error(QMediaPlayer::ResourceError, u"D3D11 device recreation failed"_s);
863 if (!m_url.isEmpty() || m_stream) {
864 setMedia(m_url, m_stream);
866 if (savedPosition > 0)
867 setPosition(savedPosition);
868 if (savedState == QMediaPlayer::PlayingState)
874 qCWarning(lcMediaEngine) <<
"TransferVideoFrame:" << QSystemError::windowsComString(hr);
879 m_deviceLostRetries = 0;
881 auto buffer =
std::make_unique<QMediaEngineVideoBuffer>(m_transferTexture, m_d3dDevice,
882 videoSize, m_textureImporter);
884 QVideoFrame frame = QVideoFramePrivate::createFrame(std::move(buffer), m_videoFormat);
886 auto startTime =
std::chrono::round<
std::chrono::microseconds>(reference_time(pts));
888 frame.setStartTime(startTime.count());
889 if (m_frameDuration > 0us)
890 frame.setEndTime((startTime + m_frameDuration).count());
892 m_videoSink->setVideoFrame(frame);
893 m_videoSink->setNativeSize(videoSize);
901 const QSize size = nativeVideoSize();
905 m_videoFormat = QVideoFrameFormat(size, QVideoFrameFormat::Format_BGRA8888);
906 m_frameDuration = videoFrameDurationUs(m_mediaEngineEx.Get());
914 const QSize size = nativeVideoSize();
916 m_videoSink->setNativeSize(size);
921 auto *newSink = sink ? sink->platformVideoSink() :
nullptr;
922 auto *oldSink = m_videoSink;
923 m_videoSink = newSink;
928 if (newSink && oldSink)
929 newSink->setVideoFrame(oldSink->currentVideoFrame());
934 if (m_audioOutput == output)
937 if (m_audioOutput && m_audioOutput->q)
938 m_audioOutput->q->disconnect(context());
940 m_audioOutput = output;
942 if (output && output->q) {
943 QObject::connect(output->q, &QAudioOutput::volumeChanged, context(), [
this] {
946 QObject::connect(output->q, &QAudioOutput::mutedChanged, context(), [
this] {
949 QObject::connect(output->q, &QAudioOutput::deviceChanged, context(), [
this] {
962 if (!m_audioOutput) {
963 m_mediaEngine->SetMuted(TRUE);
967 m_mediaEngine->SetVolume(
static_cast<
double>(m_audioOutput->volume));
968 m_mediaEngine->SetMuted(m_audioOutput->muted ? TRUE : FALSE);
970 const QByteArray id = m_audioOutput->device.id();
972 ComPtr<IMFMediaEngineAudioEndpointId> endpointId;
973 if (SUCCEEDED(m_mediaEngine.As(&endpointId))) {
974 const QString deviceId = QString::fromUtf8(id);
975 endpointId->SetAudioEndpointId(
reinterpret_cast<LPCWSTR>(deviceId.utf16()));
982 if (!m_mediaEngineEx)
987 const QSize size = nativeVideoSize();
989 m_metaData.insert(QMediaMetaData::Resolution, size);
991 if (m_duration > 0ms)
992 m_metaData.insert(QMediaMetaData::Duration, m_duration.count());
994 m_metadataFuture.cancel();
996 const QUrl url = m_url;
1004 m_metadataFuture = QtConcurrent::run([url]() -> QMediaMetaData {
1005 ComPtr<IMFSourceResolver> resolver;
1006 if (FAILED(MFCreateSourceResolver(&resolver)))
1009 ComPtr<IUnknown> sourceUnk;
1010 MF_OBJECT_TYPE objectType = MF_OBJECT_INVALID;
1011 HRESULT hr = E_FAIL;
1013 if (url.scheme() == u"qrc"_s) {
1014 auto file = std::make_unique<QFile>(u':' + url.path());
1015 if (!file->open(QIODevice::ReadOnly))
1022 ComPtr<MFStream> mfStream =
1023 makeComObject<MFStream>(file.release(),
true,
true );
1025 const auto urlStr = url.toString();
1026 hr = resolver->CreateObjectFromByteStream(
1027 mfStream.Get(),
reinterpret_cast<LPCWSTR>(urlStr.utf16()),
1028 MF_RESOLUTION_MEDIASOURCE
1029 | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE,
1030 nullptr, &objectType, &sourceUnk);
1032 const auto urlStr = url.toString();
1033 hr = resolver->CreateObjectFromURL(
1034 reinterpret_cast<LPCWSTR>(urlStr.utf16()),
1035 MF_RESOLUTION_MEDIASOURCE
1036 | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE,
1037 nullptr, &objectType, &sourceUnk);
1040 if (FAILED(hr) || !sourceUnk)
1043 ComPtr<IMFMediaSource> mediaSource;
1044 if (FAILED(sourceUnk.As(&mediaSource)))
1047 QMediaMetaData result = MFMetaData::fromNative(mediaSource.Get());
1048 mediaSource->Shutdown();
1050 }).then(context(), [
this](QMediaMetaData rich) {
1051 if (!rich.isEmpty()) {
1052 for (
const auto &[key, value] : std::as_const(m_metaData).asKeyValueRange())
1053 rich.insert(key, value);
1062 if (!m_mediaEngineEx)
1067 DWORD streamCount = 0;
1068 if (FAILED(m_mediaEngineEx->GetNumberOfStreams(&streamCount)))
1071 for (DWORD i = 0; i < streamCount; ++i) {
1072 std::optional<TrackType> trackType;
1074 ScopedPropVariant var;
1075 if (FAILED(m_mediaEngineEx->GetStreamAttribute(i, MF_MT_MAJOR_TYPE, var.get())))
1078 if (var->vt == VT_CLSID) {
1079 if (*var->puuid == MFMediaType_Audio)
1080 trackType = TrackType::AudioStream;
1081 else if (*var->puuid == MFMediaType_Video)
1082 trackType = TrackType::VideoStream;
1089 QMediaMetaData trackMeta;
1091 ScopedPropVariant var;
1092 if (SUCCEEDED(m_mediaEngineEx->GetStreamAttribute(i, MF_SD_LANGUAGE, var.get()))) {
1093 if (var->vt == VT_LPWSTR && var->pwszVal)
1094 trackMeta.insert(QMediaMetaData::Language,
1095 languageVariant(QString::fromWCharArray(var->pwszVal)));
1099 m_tracks[*trackType].entries.push_back({ trackMeta, i });
1101 BOOL selected = FALSE;
1102 if (SUCCEEDED(m_mediaEngineEx->GetStreamSelection(i, &selected)) && selected)
1103 m_tracks[*trackType].currentIndex =
1104 static_cast<
int>(m_tracks[*trackType].entries.size()) - 1;
1107 updateSubtitleTracks();
1114 auto &subtitleInfo = m_tracks[TrackType::SubtitleStream];
1120 ComPtr<IMFTimedTextTrackList> trackList;
1121 if (FAILED(m_timedText->GetTextTracks(&trackList)) || !trackList)
1124 const DWORD count = trackList->GetLength();
1125 for (DWORD i = 0; i < count; ++i) {
1126 ComPtr<IMFTimedTextTrack> track;
1127 if (FAILED(trackList->GetTrack(i, &track)) || !track)
1130 QMediaMetaData trackMeta;
1132 QComTaskResource<WCHAR> label;
1133 if (SUCCEEDED(track->GetLabel(label.address())) && label)
1134 trackMeta.insert(QMediaMetaData::Title, QString::fromWCharArray(label.get()));
1136 QComTaskResource<WCHAR> lang;
1137 if (SUCCEEDED(track->GetLanguage(lang.address())) && lang)
1138 trackMeta.insert(QMediaMetaData::Language,
1139 languageVariant(QString::fromWCharArray(lang.get())));
1142 const DWORD trackId = track->GetId();
1144 subtitleInfo.entries.push_back({ trackMeta, trackId });
1146 if (track->IsActive()) {
1147 subtitleInfo.currentIndex =
static_cast<
int>(subtitleInfo.entries.size()) - 1;
1157 const double dur = m_mediaEngine->GetDuration();
1158 const auto newDuration = std::chrono::milliseconds(
1159 (std::isfinite(dur) && dur >= 0) ? qRound64(dur * 1000.0) : 0);
1160 if (newDuration != m_duration) {
1161 m_duration = newDuration;
1162 durationChanged(m_duration.count());
1168 return static_cast<
int>(m_tracks[type].entries.size());
1173 if (trackNumber < 0 || trackNumber >=
static_cast<
int>(m_tracks[type].entries.size()))
1175 return m_tracks[type].entries.at(trackNumber).metaData;
1180 return m_tracks[type].currentIndex;
1185 if (index < -1 || index >=
static_cast<
int>(m_tracks[type].entries.size()))
1188 if (type == TrackType::SubtitleStream) {
1193 const int currentIdx = m_tracks[type].currentIndex;
1194 if (currentIdx >= 0 && currentIdx <
static_cast<
int>(m_tracks[type].entries.size())) {
1195 DWORD trackId = m_tracks[type].entries.at(currentIdx).streamIndex;
1196 m_timedText->SelectTrack(trackId, FALSE);
1200 DWORD trackId = m_tracks[type].entries.at(index).streamIndex;
1201 m_timedText->SelectTrack(trackId, TRUE);
1205 m_videoSink->setSubtitleText({});
1207 m_tracks[type].currentIndex = index;
1208 activeTracksChanged();
1212 if (!m_mediaEngineEx)
1215 const int currentIdx = m_tracks[type].currentIndex;
1216 if (currentIdx >= 0 && currentIdx <
static_cast<
int>(m_tracks[type].entries.size())) {
1217 DWORD streamIdx = m_tracks[type].entries.at(currentIdx).streamIndex;
1218 m_mediaEngineEx->SetStreamSelection(streamIdx, FALSE);
1222 DWORD streamIdx = m_tracks[type].entries.at(index).streamIndex;
1223 m_mediaEngineEx->SetStreamSelection(streamIdx, TRUE);
1226 m_tracks[type].currentIndex = index;
1227 activeTracksChanged();
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")