7#include "common/qohosvideooutput_p.h"
8#include <QtMultimedia/private/qplatformvideosink_p.h>
10#include <private/qplatformaudiooutput_p.h>
12#include <QtMultimedia/qaudiooutput.h>
13#include <QtMultimedia/qmediaplayer.h>
14#include <QtMultimedia/qmediametadata.h>
15#include <QtMultimedia/qmediatimerange.h>
16#include <QtMultimedia/qvideosink.h>
18#include <QtCore/qfile.h>
19#include <QtCore/qtimer.h>
20#include <QtCore/qfileinfo.h>
21#include <QtCore/qtemporaryfile.h>
22#include <QtCore/qloggingcategory.h>
23#include <QtCore/qmetaobject.h>
24#include <QtCore/qpointer.h>
26#include <multimedia/player_framework/native_averrors.h>
34QMediaPlayer::MediaStatus mediaStatusFor(AVPlayerState state,
bool atEnd)
37 return QMediaPlayer::EndOfMedia;
41 return QMediaPlayer::NoMedia;
43 return QMediaPlayer::LoadingMedia;
46 return QMediaPlayer::LoadedMedia;
49 return QMediaPlayer::BufferedMedia;
51 return QMediaPlayer::EndOfMedia;
54 return QMediaPlayer::InvalidMedia;
56 return QMediaPlayer::NoMedia;
59QMediaPlayer::PlaybackState playbackStateFor(AVPlayerState state)
63 return QMediaPlayer::PlayingState;
65 return QMediaPlayer::PausedState;
75 return QMediaPlayer::StoppedState;
78AVPlaybackSpeed playbackSpeedFor(qreal rate)
82 return AV_SPEED_FORWARD_0_125_X;
84 return AV_SPEED_FORWARD_0_25_X;
86 return AV_SPEED_FORWARD_0_50_X;
88 return AV_SPEED_FORWARD_0_75_X;
90 return AV_SPEED_FORWARD_1_00_X;
92 return AV_SPEED_FORWARD_1_25_X;
94 return AV_SPEED_FORWARD_1_50_X;
96 return AV_SPEED_FORWARD_1_75_X;
98 return AV_SPEED_FORWARD_2_00_X;
99 return AV_SPEED_FORWARD_3_00_X;
105 : QObject(parent), QPlatformMediaPlayer(parent)
121 return m_position.load();
128 OH_AVPlayer_Seek(m_player,
static_cast<int32_t>(position), AV_SEEK_NEXT_SYNC);
133 return m_bufferProgress;
145 return QMediaTimeRange{ 0, m_duration };
150 return m_playbackRate;
155 if (qFuzzyCompare(m_playbackRate, rate))
157 m_playbackRate = rate;
159 OH_AVPlayer_SetPlaybackSpeed(m_player, playbackSpeedFor(rate));
160 playbackRateChanged(rate);
179 OH_AVPlayer_Reset(m_player);
182 if (media.isEmpty()) {
185 m_pendingSetMedia =
false;
186 mediaStatusChanged(QMediaPlayer::NoMedia);
187 stateChanged(QMediaPlayer::StoppedState);
196 const QString scheme = media.scheme();
197 if (media.isLocalFile() || scheme.isEmpty() || scheme == QLatin1String(
"qrc")) {
199 if (media.isLocalFile())
200 openPath = media.toLocalFile();
201 else if (scheme == QLatin1String(
"qrc"))
202 openPath = QLatin1Char(
':') + media.path();
204 openPath = media.toString();
205 QFile probe(openPath);
206 if (!probe.open(QIODevice::ReadOnly)) {
207 mediaStatusChanged(QMediaPlayer::LoadingMedia);
208 setInvalidMediaWithError(QMediaPlayer::ResourceError,
209 QStringLiteral(
"Cannot open '%1'").arg(openPath));
214 m_pendingSetMedia =
false;
215 m_sourceApplied =
false;
216 m_surfaceWaitStarted =
false;
219 QMetaObject::invokeMethod(
this, &QOhosMediaPlayer::tryApplyPendingSource,
220 Qt::QueuedConnection);
225 if (m_sourceApplied || m_media.isEmpty())
231 auto *rhiSink = m_videoSink ? qobject_cast<QRhiVideoSink *>(m_videoSink->platformVideoSink())
233 const bool hasRhi = rhiSink && rhiSink->withRhi([](QRhi *rhi) {
234 return rhi !=
nullptr;
236 if (m_videoOutput && m_videoSink && !hasRhi) {
237 if (!m_surfaceWaitStarted) {
238 m_surfaceWaitStarted =
true;
239 QTimer::singleShot(surfaceWaitTimeoutMs,
this, [
this] {
240 if (!m_sourceApplied && m_pendingSetMedia && !m_media.isEmpty()) {
241 m_pendingSetMedia =
false;
242 m_sourceApplied =
true;
243 applyPendingSource();
247 m_pendingSetMedia =
true;
248 mediaStatusChanged(QMediaPlayer::LoadingMedia);
252 m_sourceApplied =
true;
253 applyPendingSource();
258 if (!ensurePlayer()) {
259 setInvalidMediaWithError(QMediaPlayer::ResourceError,
260 QStringLiteral(
"Failed to create OH_AVPlayer"));
264 OH_AVPlayer_SetAudioRendererInfo(m_player, AUDIOSTREAM_USAGE_MOVIE);
266 m_videoSurfaceAttached =
false;
271 mediaStatusChanged(QMediaPlayer::LoadingMedia);
273 OH_AVErrCode result = AV_ERR_OK;
274 const QString scheme = m_media.scheme();
275 const bool isQrc = scheme == QLatin1String(
"qrc");
276 const bool isFileLike = m_media.isLocalFile() || scheme.isEmpty() || isQrc;
279 if (m_media.isLocalFile()) {
280 openPath = m_media.toLocalFile();
283 openPath = QLatin1Char(
':') + m_media.path();
286 openPath = m_media.toString();
288 auto file = std::make_unique<QFile>(openPath);
289 if (!file->open(QIODevice::ReadOnly)) {
290 setInvalidMediaWithError(QMediaPlayer::ResourceError,
291 QStringLiteral(
"Cannot open '%1'").arg(openPath));
294 const int fd = file->handle();
295 const qint64 size = file->size();
300 auto temp = std::make_unique<QTemporaryFile>();
301 if (!temp->open() || temp->write(file->readAll()) < 0) {
302 setInvalidMediaWithError(QMediaPlayer::ResourceError,
303 QStringLiteral(
"Cannot stage '%1'").arg(openPath));
308 const int tempFd = temp->handle();
309 const qint64 tempSize = temp->size();
310 result = OH_AVPlayer_SetFDSource(m_player, tempFd, 0, tempSize);
311 m_sourceFile = std::move(temp);
313 result = OH_AVPlayer_SetFDSource(m_player, fd, 0, size);
314 m_sourceFile = std::move(file);
317 const QByteArray url = m_media.toString(QUrl::FullyEncoded).toUtf8();
318 result = OH_AVPlayer_SetURLSource(m_player, url.constData());
321 if (result != AV_ERR_OK) {
322 qCWarning(qLcOhosMediaPlugin) <<
"Source rejected, error" <<
int(result);
323 setInvalidMediaWithError(QMediaPlayer::ResourceError,
324 QStringLiteral(
"OH_AVPlayer rejected the source"));
330 if (
auto *window = m_videoOutput->nativeWindow()) {
331 if (OH_AVPlayer_SetVideoSurface(m_player, window) == AV_ERR_OK)
332 m_videoSurfaceAttached =
true;
334 qCWarning(qLcOhosMediaPlugin) <<
"OH_AVPlayer_SetVideoSurface failed";
338 result = OH_AVPlayer_Prepare(m_player);
339 if (result != AV_ERR_OK) {
340 qCWarning(qLcOhosMediaPlugin) <<
"Prepare failed, error" <<
int(result);
341 setInvalidMediaWithError(QMediaPlayer::ResourceError,
342 QStringLiteral(
"OH_AVPlayer_Prepare failed"));
350 m_pendingPlay = !m_media.isEmpty();
354 if (m_avState == AV_PREPARED || m_avState == AV_PAUSED || m_avState == AV_COMPLETED
355 || m_avState == AV_STOPPED) {
356 m_pendingPlay =
false;
357 OH_AVPlayer_Play(m_player);
360 m_pendingPlay =
true;
366 m_pendingPlay =
false;
367 if (m_player && m_avState == AV_PLAYING)
368 OH_AVPlayer_Pause(m_player);
373 m_pendingPlay =
false;
376 if (m_avState == AV_PLAYING || m_avState == AV_PAUSED || m_avState == AV_PREPARED
377 || m_avState == AV_COMPLETED)
378 OH_AVPlayer_Stop(m_player);
383 m_audioOutput = output;
392 m_videoOutput.reset();
396 if (!m_videoOutput) {
397 m_videoOutput = std::make_unique<QOhosVideoOutput>(
398 sink, QOhosVideoOutput::ContentSource::MediaPlayer,
this);
399 connect(m_videoOutput.get(), &QOhosVideoOutput::surfaceReady,
this,
410 case TrackType::VideoStream:
411 return m_hasVideoTrack ? 1 : 0;
412 case TrackType::AudioStream:
413 return m_hasAudioTrack ? 1 : 0;
414 case TrackType::SubtitleStream:
422 if (streamNumber != 0 || trackCount(type) == 0)
425 md.insert(QMediaMetaData::Language, QVariant::fromValue(QLocale::AnyLanguage));
433 md.insert(QMediaMetaData::Duration, m_duration);
434 if (m_videoWidth > 0 && m_videoHeight > 0)
435 md.insert(QMediaMetaData::Resolution, QSize{ m_videoWidth, m_videoHeight });
441 return trackCount(type) > 0 ? 0 : -1;
446 if (m_pendingSetMedia && !m_media.isEmpty()) {
447 m_pendingSetMedia =
false;
448 m_sourceApplied =
true;
449 applyPendingSource();
455 if (m_avState == newState)
457 m_avState = newState;
463 if (newState == AV_IDLE)
466 if (newState == AV_PREPARED) {
467 int32_t durationMs = 0;
468 if (OH_AVPlayer_GetDuration(m_player, &durationMs) == AV_ERR_OK) {
469 if (m_duration != durationMs) {
470 m_duration = durationMs;
471 durationChanged(m_duration);
474 m_seekable = m_duration > 0;
475 seekableChanged(m_seekable);
477 int32_t videoWidth = 0;
478 int32_t videoHeight = 0;
479 OH_AVPlayer_GetVideoWidth(m_player, &videoWidth);
480 OH_AVPlayer_GetVideoHeight(m_player, &videoHeight);
481 const bool hasVideo = videoWidth > 0 && videoHeight > 0;
482 if (hasVideo && m_videoOutput)
483 m_videoOutput->setVideoSize(QSize{ videoWidth, videoHeight });
484 videoAvailableChanged(hasVideo);
485 audioAvailableChanged(
true);
486 const bool prevHasVideo = m_hasVideoTrack;
487 const bool prevHasAudio = m_hasAudioTrack;
488 m_hasVideoTrack = hasVideo;
489 m_hasAudioTrack =
true;
490 if (prevHasVideo != m_hasVideoTrack || prevHasAudio != m_hasAudioTrack) {
492 activeTracksChanged();
494 m_videoWidth = videoWidth;
495 m_videoHeight = videoHeight;
499 mediaStatusChanged(mediaStatusFor(newState,
false));
500 stateChanged(playbackStateFor(newState));
502 if (newState == AV_PREPARED && m_pendingPlay) {
503 m_pendingPlay =
false;
504 OH_AVPlayer_Play(m_player);
510 mediaStatusChanged(QMediaPlayer::EndOfMedia);
511 stateChanged(QMediaPlayer::StoppedState);
525 OH_AVPlayer_GetVideoWidth(m_player, &width);
526 OH_AVPlayer_GetVideoHeight(m_player, &height);
527 m_videoWidth = width;
528 m_videoHeight = height;
529 if (m_videoOutput && width > 0 && height > 0)
530 m_videoOutput->setVideoSize(QSize{ width, height });
531 videoAvailableChanged(width > 0 && height > 0);
536 const float progress = std::clamp(bufferingPercent / 100.0f, 0.0f, 1.0f);
537 if (qFuzzyCompare(m_bufferProgress, progress))
539 m_bufferProgress = progress;
540 bufferProgressChanged(progress);
543void QOhosMediaPlayer::handleError(int32_t errorCode,
const QString &errorMsg)
545 qCWarning(qLcOhosMediaPlugin) <<
"OH_AVPlayer error" << errorCode << errorMsg;
546 setInvalidMediaWithError(QMediaPlayer::ResourceError,
547 errorMsg.isEmpty() ? QStringLiteral(
"AVPlayer error %1").arg(errorCode)
555 OH_AVPlayer_SetOnInfoCallback(m_player,
nullptr,
nullptr);
556 OH_AVPlayer_SetOnErrorCallback(m_player,
nullptr,
nullptr);
557 OH_AVPlayer_ReleaseSync(m_player);
564 m_sourceFile.reset();
565 const bool changed = m_hasVideoTrack || m_hasAudioTrack;
566 m_hasVideoTrack =
false;
567 m_hasAudioTrack =
false;
568 const bool hadMetadata = m_duration > 0 || m_videoWidth > 0 || m_videoHeight > 0;
573 activeTracksChanged();
583 m_player = OH_AVPlayer_Create();
585 qCWarning(qLcOhosMediaPlugin) <<
"OH_AVPlayer_Create failed";
588 OH_AVPlayer_SetOnInfoCallback(m_player, &QOhosMediaPlayer::onInfoTrampoline,
this);
589 OH_AVPlayer_SetOnErrorCallback(m_player, &QOhosMediaPlayer::onErrorTrampoline,
this);
600 volume = m_audioOutput->muted ? 0.0f : m_audioOutput->volume;
602 OH_AVPlayer_SetVolume(m_player, volume, volume);
605void QOhosMediaPlayer::onInfoTrampoline(OH_AVPlayer *player, AVPlayerOnInfoType type,
606 OH_AVFormat *body,
void *userData)
615 case AV_INFO_TYPE_STATE_CHANGE: {
616 AVPlayerState state{ AV_IDLE };
617 OH_AVPlayer_GetState(player, &state);
618 QMetaObject::invokeMethod(
619 self, [self, state]() { self->handleStateChange(state); }, Qt::QueuedConnection);
622 case AV_INFO_TYPE_POSITION_UPDATE: {
624 OH_AVPlayer_GetCurrentTime(player, &pos);
625 self->m_position.store(pos);
626 QMetaObject::invokeMethod(
627 self, [self, pos]() { self->positionChanged(qint64{ pos }); },
628 Qt::QueuedConnection);
631 case AV_INFO_TYPE_DURATION_UPDATE: {
633 OH_AVPlayer_GetDuration(player, &dur);
634 QMetaObject::invokeMethod(
637 if (self->m_duration == dur)
639 self->m_duration = dur;
640 self->durationChanged(qint64{ dur });
642 Qt::QueuedConnection);
645 case AV_INFO_TYPE_EOS:
646 QMetaObject::invokeMethod(
647 self, [self]() { self->handleEndOfStream(); }, Qt::QueuedConnection);
649 case AV_INFO_TYPE_SEEKDONE:
650 QMetaObject::invokeMethod(
651 self, [self]() { self->handleSeekDone(); }, Qt::QueuedConnection);
653 case AV_INFO_TYPE_RESOLUTION_CHANGE:
654 QMetaObject::invokeMethod(
655 self, [self]() { self->handleResolutionChange(); }, Qt::QueuedConnection);
662void QOhosMediaPlayer::onErrorTrampoline(OH_AVPlayer *, int32_t errorCode,
const char *errorMsg,
668 QString message = errorMsg ? QString::fromUtf8(errorMsg) : QString{};
669 QMetaObject::invokeMethod(
670 self, [self, errorCode, message]() { self->handleError(errorCode, message); },
671 Qt::QueuedConnection);
676#include "moc_qohosmediaplayer_p.cpp"
Combined button and popup list for selecting options.