Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "androidmediaplayer_p.h"
#include "androidsurfacetexture_p.h"
#include <QList>
#include <QReadWriteLock>
#include <QString>
#include <QtCore/qcoreapplication.h>
#include <qloggingcategory.h>
static const char QtAndroidMediaPlayerClassName[] = "org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer";
typedef QList<AndroidMediaPlayer *> MediaPlayerList;
Q_GLOBAL_STATIC(MediaPlayerList, mediaPlayers)
Q_GLOBAL_STATIC(QReadWriteLock, rwLock)
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcAudio, "qt.multimedia.audio");
AndroidMediaPlayer::AndroidMediaPlayer()
: QObject()
{
QWriteLocker locker(rwLock);
auto context = QNativeInterface::QAndroidApplication::context();
const jlong id = reinterpret_cast<jlong>(this);
mMediaPlayer = QJniObject(QtAndroidMediaPlayerClassName,
"(Landroid/content/Context;J)V",
context.object(),
id);
mediaPlayers->append(this);
}
AndroidMediaPlayer::~AndroidMediaPlayer()
{
QWriteLocker locker(rwLock);
const int i = mediaPlayers->indexOf(this);
Q_ASSERT(i != -1);
mediaPlayers->remove(i);
}
void AndroidMediaPlayer::release()
{
mMediaPlayer.callMethod<void>("release");
}
void AndroidMediaPlayer::reset()
{
mMediaPlayer.callMethod<void>("reset");
}
int AndroidMediaPlayer::getCurrentPosition()
{
return mMediaPlayer.callMethod<jint>("getCurrentPosition");
}
int AndroidMediaPlayer::getDuration()
{
return mMediaPlayer.callMethod<jint>("getDuration");
}
bool AndroidMediaPlayer::isPlaying()
{
return mMediaPlayer.callMethod<jboolean>("isPlaying");
}
int AndroidMediaPlayer::volume()
{
return mMediaPlayer.callMethod<jint>("getVolume");
}
bool AndroidMediaPlayer::isMuted()
{
return mMediaPlayer.callMethod<jboolean>("isMuted");
}
qreal AndroidMediaPlayer::playbackRate()
{
qreal rate(1.0);
if (QNativeInterface::QAndroidApplication::sdkVersion() < 23)
return rate;
QJniObject player = mMediaPlayer.callObjectMethod("getMediaPlayerHandle",
"()Landroid/media/MediaPlayer;");
if (player.isValid()) {
QJniObject playbackParams = player.callObjectMethod("getPlaybackParams",
"()Landroid/media/PlaybackParams;");
if (playbackParams.isValid()) {
QJniEnvironment env;
auto methodId = env->GetMethodID(playbackParams.objectClass(), "getSpeed", "()F");
const qreal speed = env->CallFloatMethod(playbackParams.object(), methodId);
if (!env.checkAndClearExceptions())
rate = speed;
}
}
return rate;
}
jobject AndroidMediaPlayer::display()
{
return mMediaPlayer.callObjectMethod("display", "()Landroid/view/SurfaceHolder;").object();
}
AndroidMediaPlayer::TrackInfo convertTrackInfo(int streamNumber, QJniObject androidTrackInfo)
{
const QLatin1String unknownMimeType("application/octet-stream");
const QLatin1String undefinedLanguage("und");
if (!androidTrackInfo.isValid())
return { streamNumber, AndroidMediaPlayer::TrackType::Unknown, undefinedLanguage,
unknownMimeType };
QJniEnvironment env;
auto methodId = env->GetMethodID(androidTrackInfo.objectClass(), "getType", "()I");
const jint type = env->CallIntMethod(androidTrackInfo.object(), methodId);
if (env.checkAndClearExceptions())
return { streamNumber, AndroidMediaPlayer::TrackType::Unknown, undefinedLanguage,
unknownMimeType };
if (type < 0 || type > 5) {
return { streamNumber, AndroidMediaPlayer::TrackType::Unknown, undefinedLanguage,
unknownMimeType };
}
AndroidMediaPlayer::TrackType trackType = static_cast<AndroidMediaPlayer::TrackType>(type);
auto languageObject = androidTrackInfo.callObjectMethod("getLanguage", "()Ljava/lang/String;");
QString language = languageObject.isValid() ? languageObject.toString() : undefinedLanguage;
auto mimeTypeObject = androidTrackInfo.callObjectMethod("getMime", "()Ljava/lang/String;");
QString mimeType = mimeTypeObject.isValid() ? mimeTypeObject.toString() : unknownMimeType;
return { streamNumber, trackType, language, mimeType };
}
QList<AndroidMediaPlayer::TrackInfo> AndroidMediaPlayer::tracksInfo()
{
auto androidTracksInfoObject = mMediaPlayer.callObjectMethod(
"getAllTrackInfo",
"()[Lorg/qtproject/qt/android/multimedia/QtAndroidMediaPlayer$TrackInfo;");
if (!androidTracksInfoObject.isValid())
return QList<AndroidMediaPlayer::TrackInfo>();
auto androidTracksInfo = androidTracksInfoObject.object<jobjectArray>();
if (!androidTracksInfo)
return QList<AndroidMediaPlayer::TrackInfo>();
QJniEnvironment environment;
auto numberofTracks = environment->GetArrayLength(androidTracksInfo);
QList<AndroidMediaPlayer::TrackInfo> tracksInformation;
for (int index = 0; index < numberofTracks; index++) {
auto androidTrackInformation = environment->GetObjectArrayElement(androidTracksInfo, index);
if (environment.checkAndClearExceptions()) {
continue;
}
auto trackInfo = convertTrackInfo(index, androidTrackInformation);
tracksInformation.insert(index, trackInfo);
environment->DeleteLocalRef(androidTrackInformation);
}
return tracksInformation;
}
int AndroidMediaPlayer::activeTrack(TrackType androidTrackType)
{
int type = static_cast<int>(androidTrackType);
return mMediaPlayer.callMethod<jint>("getSelectedTrack", "(I)I", type);
}
void AndroidMediaPlayer::deselectTrack(int trackNumber)
{
mMediaPlayer.callMethod<void>("deselectTrack", "(I)V", trackNumber);
}
void AndroidMediaPlayer::selectTrack(int trackNumber)
{
mMediaPlayer.callMethod<void>("selectTrack", "(I)V", trackNumber);
}
void AndroidMediaPlayer::play()
{
mMediaPlayer.callMethod<void>("start");
}
void AndroidMediaPlayer::pause()
{
mMediaPlayer.callMethod<void>("pause");
}
void AndroidMediaPlayer::stop()
{
mMediaPlayer.callMethod<void>("stop");
}
void AndroidMediaPlayer::seekTo(qint32 msec)
{
mMediaPlayer.callMethod<void>("seekTo", "(I)V", jint(msec));
}
void AndroidMediaPlayer::setMuted(bool mute)
{
if (mAudioBlocked)
return;
mMediaPlayer.callMethod<void>("mute", "(Z)V", jboolean(mute));
}
void AndroidMediaPlayer::setDataSource(const QNetworkRequest &request)
{
QJniObject string = QJniObject::fromString(request.url().toString(QUrl::FullyEncoded));
mMediaPlayer.callMethod<void>("initHeaders", "()V");
for (auto &header : request.rawHeaderList()) {
auto value = request.rawHeader(header);
mMediaPlayer.callMethod<void>("setHeader", "(Ljava/lang/String;Ljava/lang/String;)V",
QJniObject::fromString(QLatin1String(header)).object(),
QJniObject::fromString(QLatin1String(value)).object());
}
mMediaPlayer.callMethod<void>("setDataSource", "(Ljava/lang/String;)V", string.object());
}
void AndroidMediaPlayer::prepareAsync()
{
mMediaPlayer.callMethod<void>("prepareAsync");
}
void AndroidMediaPlayer::setVolume(int volume)
{
if (mAudioBlocked)
return;
mMediaPlayer.callMethod<void>("setVolume", "(I)V", jint(volume));
}
void AndroidMediaPlayer::blockAudio()
{
mAudioBlocked = true;
}
void AndroidMediaPlayer::unblockAudio()
{
mAudioBlocked = false;
}
void AndroidMediaPlayer::startSoundStreaming(const int inputId, const int outputId)
{
QJniObject::callStaticMethod<void>("org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
"startSoundStreaming",
inputId,
outputId);
}
void AndroidMediaPlayer::stopSoundStreaming()
{
QJniObject::callStaticMethod<void>(
"org/qtproject/qt/android/multimedia/QtAudioDeviceManager", "stopSoundStreaming");
}
bool AndroidMediaPlayer::setPlaybackRate(qreal rate)
{
if (QNativeInterface::QAndroidApplication::sdkVersion() < 23) {
qWarning() << "Setting the playback rate on a media player requires"
<< "Android 6.0 (API level 23) or later";
return false;
}
return mMediaPlayer.callMethod<jboolean>("setPlaybackRate", jfloat(rate));
}
void AndroidMediaPlayer::setDisplay(AndroidSurfaceTexture *surfaceTexture)
{
mMediaPlayer.callMethod<void>("setDisplay",
"(Landroid/view/SurfaceHolder;)V",
surfaceTexture ? surfaceTexture->surfaceHolder() : 0);
}
bool AndroidMediaPlayer::setAudioOutput(const QByteArray &deviceId)
{
const bool ret = QJniObject::callStaticMethod<jboolean>(
"org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
"prepareAudioOutput",
"(I)Z",
deviceId.toInt());
if (!ret)
qCWarning(lcAudio) << "Output device not set";
return ret;
}
#if 0
void AndroidMediaPlayer::setAudioRole(QAudio::Role role)
{
QString r;
switch (role) {
case QAudio::MusicRole:
r = QLatin1String("CONTENT_TYPE_MUSIC");
break;
case QAudio::VideoRole:
r = QLatin1String("CONTENT_TYPE_MOVIE");
break;
case QAudio::VoiceCommunicationRole:
r = QLatin1String("USAGE_VOICE_COMMUNICATION");
break;
case QAudio::AlarmRole:
r = QLatin1String("USAGE_ALARM");
break;
case QAudio::NotificationRole:
r = QLatin1String("USAGE_NOTIFICATION");
break;
case QAudio::RingtoneRole:
r = QLatin1String("USAGE_NOTIFICATION_RINGTONE");
break;
case QAudio::AccessibilityRole:
r = QLatin1String("USAGE_ASSISTANCE_ACCESSIBILITY");
break;
case QAudio::SonificationRole:
r = QLatin1String("CONTENT_TYPE_SONIFICATION");
break;
case QAudio::GameRole:
r = QLatin1String("USAGE_GAME");
break;
default:
return;
}
int type = 0; // CONTENT_TYPE_UNKNOWN
int usage = 0; // USAGE_UNKNOWN
if (r == QLatin1String("CONTENT_TYPE_MOVIE"))
type = 3;
else if (r == QLatin1String("CONTENT_TYPE_MUSIC"))
type = 2;
else if (r == QLatin1String("CONTENT_TYPE_SONIFICATION"))
type = 4;
else if (r == QLatin1String("CONTENT_TYPE_SPEECH"))
type = 1;
else if (r == QLatin1String("USAGE_ALARM"))
usage = 4;
else if (r == QLatin1String("USAGE_ASSISTANCE_ACCESSIBILITY"))
usage = 11;
else if (r == QLatin1String("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"))
usage = 12;
else if (r == QLatin1String("USAGE_ASSISTANCE_SONIFICATION"))
usage = 13;
else if (r == QLatin1String("USAGE_ASSISTANT"))
usage = 16;
else if (r == QLatin1String("USAGE_GAME"))
usage = 14;
else if (r == QLatin1String("USAGE_MEDIA"))
usage = 1;
else if (r == QLatin1String("USAGE_NOTIFICATION"))
usage = 5;
else if (r == QLatin1String("USAGE_NOTIFICATION_COMMUNICATION_DELAYED"))
usage = 9;
else if (r == QLatin1String("USAGE_NOTIFICATION_COMMUNICATION_INSTANT"))
usage = 8;
else if (r == QLatin1String("USAGE_NOTIFICATION_COMMUNICATION_REQUEST"))
usage = 7;
else if (r == QLatin1String("USAGE_NOTIFICATION_EVENT"))
usage = 10;
else if (r == QLatin1String("USAGE_NOTIFICATION_RINGTONE"))
usage = 6;
else if (r == QLatin1String("USAGE_VOICE_COMMUNICATION"))
usage = 2;
else if (r == QLatin1String("USAGE_VOICE_COMMUNICATION_SIGNALLING"))
usage = 3;
mMediaPlayer.callMethod<void>("setAudioAttributes", "(II)V", jint(type), jint(usage));
}
#endif
static void onErrorNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QReadLocker locker(rwLock);
const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
if (Q_UNLIKELY(i == -1))
return;
Q_EMIT (*mediaPlayers)[i]->error(what, extra);
}
static void onBufferingUpdateNative(JNIEnv *env, jobject thiz, jint percent, jlong id)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QReadLocker locker(rwLock);
const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
if (Q_UNLIKELY(i == -1))
return;
Q_EMIT (*mediaPlayers)[i]->bufferingChanged(percent);
}
static void onProgressUpdateNative(JNIEnv *env, jobject thiz, jint progress, jlong id)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QReadLocker locker(rwLock);
const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
if (Q_UNLIKELY(i == -1))
return;
Q_EMIT (*mediaPlayers)[i]->progressChanged(progress);
}
static void onDurationChangedNative(JNIEnv *env, jobject thiz, jint duration, jlong id)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QReadLocker locker(rwLock);
const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
if (Q_UNLIKELY(i == -1))
return;
Q_EMIT (*mediaPlayers)[i]->durationChanged(duration);
}
static void onInfoNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QReadLocker locker(rwLock);
const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
if (Q_UNLIKELY(i == -1))
return;
Q_EMIT (*mediaPlayers)[i]->info(what, extra);
}
static void onStateChangedNative(JNIEnv *env, jobject thiz, jint state, jlong id)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QReadLocker locker(rwLock);
const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
if (Q_UNLIKELY(i == -1))
return;
Q_EMIT (*mediaPlayers)[i]->stateChanged(state);
}
static void onVideoSizeChangedNative(JNIEnv *env,
jobject thiz,
jint width,
jint height,
jlong id)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QReadLocker locker(rwLock);
const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
if (Q_UNLIKELY(i == -1))
return;
Q_EMIT (*mediaPlayers)[i]->videoSizeChanged(width, height);
}
static AndroidMediaPlayer *getMediaPlayer(jlong ptr)
{
auto mediaplayer = reinterpret_cast<AndroidMediaPlayer *>(ptr);
if (!mediaplayer || !mediaPlayers->contains(mediaplayer))
return nullptr;
return mediaplayer;
}
static void onTrackInfoChangedNative(JNIEnv *env, jobject thiz, jlong ptr)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QReadLocker locker(rwLock);
auto mediaplayer = getMediaPlayer(ptr);
if (!mediaplayer)
return;
Q_EMIT mediaplayer->tracksInfoChanged();
}
static void onTimedTextChangedNative(JNIEnv *env, jobject thiz, jstring timedText, jint time,
jlong ptr)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
Q_UNUSED(time);
QReadLocker locker(rwLock);
auto mediaplayer = getMediaPlayer(ptr);
if (!mediaplayer)
return;
QString subtitleText;
if (timedText != nullptr)
subtitleText = QString::fromUtf8(env->GetStringUTFChars(timedText, 0));
Q_EMIT mediaplayer->timedTextChanged(subtitleText);
}
bool AndroidMediaPlayer::registerNativeMethods()
{
static const JNINativeMethod methods[] = {
{ "onErrorNative", "(IIJ)V", reinterpret_cast<void *>(onErrorNative) },
{ "onBufferingUpdateNative", "(IJ)V", reinterpret_cast<void *>(onBufferingUpdateNative) },
{ "onProgressUpdateNative", "(IJ)V", reinterpret_cast<void *>(onProgressUpdateNative) },
{ "onDurationChangedNative", "(IJ)V", reinterpret_cast<void *>(onDurationChangedNative) },
{ "onInfoNative", "(IIJ)V", reinterpret_cast<void *>(onInfoNative) },
{ "onVideoSizeChangedNative", "(IIJ)V",
reinterpret_cast<void *>(onVideoSizeChangedNative) },
{ "onStateChangedNative", "(IJ)V", reinterpret_cast<void *>(onStateChangedNative) },
{ "onTrackInfoChangedNative", "(J)V", reinterpret_cast<void *>(onTrackInfoChangedNative) },
{ "onTimedTextChangedNative", "(Ljava/lang/String;IJ)V",
reinterpret_cast<void *>(onTimedTextChangedNative) }
};
const int size = std::size(methods);
return QJniEnvironment().registerNativeMethods(QtAndroidMediaPlayerClassName, methods, size);
}
QT_END_NAMESPACE
#include "moc_androidmediaplayer_p.cpp"