Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
androidmediaplayer.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
7#include <QList>
8#include <QReadWriteLock>
9#include <QString>
10#include <QtCore/qcoreapplication.h>
11#include <qloggingcategory.h>
12
13static const char QtAndroidMediaPlayerClassName[] = "org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer";
15Q_GLOBAL_STATIC(MediaPlayerList, mediaPlayers)
16Q_GLOBAL_STATIC(QReadWriteLock, rwLock)
17
18QT_BEGIN_NAMESPACE
19
20Q_STATIC_LOGGING_CATEGORY(lcAudio, "qt.multimedia.audio");
21
22AndroidMediaPlayer::AndroidMediaPlayer()
23 : QObject()
24{
25 QWriteLocker locker(rwLock);
26 auto context = QNativeInterface::QAndroidApplication::context();
27 const jlong id = reinterpret_cast<jlong>(this);
28 mMediaPlayer = QJniObject(QtAndroidMediaPlayerClassName,
29 "(Landroid/content/Context;J)V",
30 context.object(),
31 id);
32 mediaPlayers->append(this);
33}
34
36{
37 QWriteLocker locker(rwLock);
38 const int i = mediaPlayers->indexOf(this);
39 Q_ASSERT(i != -1);
40 mediaPlayers->remove(i);
41}
42
44{
45 mMediaPlayer.callMethod<void>("release");
46}
47
49{
50 mMediaPlayer.callMethod<void>("reset");
51}
52
54{
55 return mMediaPlayer.callMethod<jint>("getCurrentPosition");
56}
57
59{
60 return mMediaPlayer.callMethod<jint>("getDuration");
61}
62
64{
65 return mMediaPlayer.callMethod<jboolean>("isPlaying");
66}
67
69{
70 return mMediaPlayer.callMethod<jint>("getVolume");
71}
72
74{
75 return mMediaPlayer.callMethod<jboolean>("isMuted");
76}
77
79{
80 qreal rate(1.0);
81
82 if (QNativeInterface::QAndroidApplication::sdkVersion() < 23)
83 return rate;
84
85 QJniObject player = mMediaPlayer.callObjectMethod("getMediaPlayerHandle",
86 "()Landroid/media/MediaPlayer;");
87 if (player.isValid()) {
88 QJniObject playbackParams = player.callObjectMethod("getPlaybackParams",
89 "()Landroid/media/PlaybackParams;");
90 if (playbackParams.isValid()) {
91 QJniEnvironment env;
92 auto methodId = env->GetMethodID(playbackParams.objectClass(), "getSpeed", "()F");
93 const qreal speed = env->CallFloatMethod(playbackParams.object(), methodId);
94 if (!env.checkAndClearExceptions())
95 rate = speed;
96 }
97 }
98
99 return rate;
100}
101
103{
104 return mMediaPlayer.callObjectMethod("display", "()Landroid/view/SurfaceHolder;").object();
105}
106
107AndroidMediaPlayer::TrackInfo convertTrackInfo(int streamNumber, QJniObject androidTrackInfo)
108{
109 const QLatin1String unknownMimeType("application/octet-stream");
110 const QLatin1String undefinedLanguage("und");
111
112 if (!androidTrackInfo.isValid())
113 return { streamNumber, AndroidMediaPlayer::TrackType::Unknown, undefinedLanguage,
114 unknownMimeType };
115
116 QJniEnvironment env;
117 auto methodId = env->GetMethodID(androidTrackInfo.objectClass(), "getType", "()I");
118 const jint type = env->CallIntMethod(androidTrackInfo.object(), methodId);
119 if (env.checkAndClearExceptions())
120 return { streamNumber, AndroidMediaPlayer::TrackType::Unknown, undefinedLanguage,
121 unknownMimeType };
122
123 if (type < 0 || type > 5) {
124 return { streamNumber, AndroidMediaPlayer::TrackType::Unknown, undefinedLanguage,
125 unknownMimeType };
126 }
127
128 AndroidMediaPlayer::TrackType trackType = static_cast<AndroidMediaPlayer::TrackType>(type);
129
130 auto languageObject = androidTrackInfo.callObjectMethod("getLanguage", "()Ljava/lang/String;");
131 QString language = languageObject.isValid() ? languageObject.toString() : undefinedLanguage;
132
133 auto mimeTypeObject = androidTrackInfo.callObjectMethod("getMime", "()Ljava/lang/String;");
134 QString mimeType = mimeTypeObject.isValid() ? mimeTypeObject.toString() : unknownMimeType;
135
136 return { streamNumber, trackType, language, mimeType };
137}
138
140{
141 auto androidTracksInfoObject = mMediaPlayer.callObjectMethod(
142 "getAllTrackInfo",
143 "()[Lorg/qtproject/qt/android/multimedia/QtAndroidMediaPlayer$TrackInfo;");
144
145 if (!androidTracksInfoObject.isValid())
146 return QList<AndroidMediaPlayer::TrackInfo>();
147
148 auto androidTracksInfo = androidTracksInfoObject.object<jobjectArray>();
149 if (!androidTracksInfo)
150 return QList<AndroidMediaPlayer::TrackInfo>();
151
152 QJniEnvironment environment;
153 auto numberofTracks = environment->GetArrayLength(androidTracksInfo);
154
155 QList<AndroidMediaPlayer::TrackInfo> tracksInformation;
156
157 for (int index = 0; index < numberofTracks; index++) {
158 auto androidTrackInformation = environment->GetObjectArrayElement(androidTracksInfo, index);
159
160 if (environment.checkAndClearExceptions()) {
161 continue;
162 }
163
164 auto trackInfo = convertTrackInfo(index, androidTrackInformation);
165 tracksInformation.insert(index, trackInfo);
166
167 environment->DeleteLocalRef(androidTrackInformation);
168 }
169
170 return tracksInformation;
171}
172
174{
175 int type = static_cast<int>(androidTrackType);
176 return mMediaPlayer.callMethod<jint>("getSelectedTrack", "(I)I", type);
177}
178
179void AndroidMediaPlayer::deselectTrack(int trackNumber)
180{
181 mMediaPlayer.callMethod<void>("deselectTrack", "(I)V", trackNumber);
182}
183
184void AndroidMediaPlayer::selectTrack(int trackNumber)
185{
186 mMediaPlayer.callMethod<void>("selectTrack", "(I)V", trackNumber);
187}
188
190{
191 mMediaPlayer.callMethod<void>("start");
192}
193
195{
196 mMediaPlayer.callMethod<void>("pause");
197}
198
200{
201 mMediaPlayer.callMethod<void>("stop");
202}
203
204void AndroidMediaPlayer::seekTo(qint32 msec)
205{
206 mMediaPlayer.callMethod<void>("seekTo", "(I)V", jint(msec));
207}
208
210{
211 if (mAudioBlocked)
212 return;
213
214 mMediaPlayer.callMethod<void>("mute", "(Z)V", jboolean(mute));
215}
216
217void AndroidMediaPlayer::setDataSource(const QNetworkRequest &request)
218{
219 QJniObject string = QJniObject::fromString(request.url().toString(QUrl::FullyEncoded));
220
221 mMediaPlayer.callMethod<void>("initHeaders", "()V");
222 for (auto &header : request.rawHeaderList()) {
223 auto value = request.rawHeader(header);
224 mMediaPlayer.callMethod<void>("setHeader", "(Ljava/lang/String;Ljava/lang/String;)V",
225 QJniObject::fromString(QLatin1String(header)).object(),
226 QJniObject::fromString(QLatin1String(value)).object());
227 }
228
229 mMediaPlayer.callMethod<void>("setDataSource", "(Ljava/lang/String;)V", string.object());
230}
231
233{
234 mMediaPlayer.callMethod<void>("prepareAsync");
235}
236
238{
239 if (mAudioBlocked)
240 return;
241
242 mMediaPlayer.callMethod<void>("setVolume", "(I)V", jint(volume));
243}
244
246{
247 mAudioBlocked = true;
248}
249
251{
252 mAudioBlocked = false;
253}
254
255void AndroidMediaPlayer::startSoundStreaming(const int inputId, const int outputId)
256{
257 QJniObject::callStaticMethod<void>("org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
258 "startSoundStreaming",
259 inputId,
260 outputId);
261}
262
264{
265 QJniObject::callStaticMethod<void>(
266 "org/qtproject/qt/android/multimedia/QtAudioDeviceManager", "stopSoundStreaming");
267}
268
270{
271 if (QNativeInterface::QAndroidApplication::sdkVersion() < 23) {
272 qWarning() << "Setting the playback rate on a media player requires"
273 << "Android 6.0 (API level 23) or later";
274 return false;
275 }
276
277 return mMediaPlayer.callMethod<jboolean>("setPlaybackRate", jfloat(rate));
278}
279
281{
282 mMediaPlayer.callMethod<void>("setDisplay",
283 "(Landroid/view/SurfaceHolder;)V",
284 surfaceTexture ? surfaceTexture->surfaceHolder() : 0);
285}
286
287bool AndroidMediaPlayer::setAudioOutput(const QByteArray &deviceId)
288{
289 const bool ret = QJniObject::callStaticMethod<jboolean>(
290 "org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
291 "prepareAudioOutput",
292 "(I)Z",
293 deviceId.toInt());
294
295 if (!ret)
296 qCWarning(lcAudio) << "Output device not set";
297
298 return ret;
299}
300
301#if 0
302void AndroidMediaPlayer::setAudioRole(QAudio::Role role)
303{
304 QString r;
305 switch (role) {
306 case QAudio::MusicRole:
307 r = QLatin1String("CONTENT_TYPE_MUSIC");
308 break;
309 case QAudio::VideoRole:
310 r = QLatin1String("CONTENT_TYPE_MOVIE");
311 break;
312 case QAudio::VoiceCommunicationRole:
313 r = QLatin1String("USAGE_VOICE_COMMUNICATION");
314 break;
315 case QAudio::AlarmRole:
316 r = QLatin1String("USAGE_ALARM");
317 break;
318 case QAudio::NotificationRole:
319 r = QLatin1String("USAGE_NOTIFICATION");
320 break;
321 case QAudio::RingtoneRole:
322 r = QLatin1String("USAGE_NOTIFICATION_RINGTONE");
323 break;
324 case QAudio::AccessibilityRole:
325 r = QLatin1String("USAGE_ASSISTANCE_ACCESSIBILITY");
326 break;
327 case QAudio::SonificationRole:
328 r = QLatin1String("CONTENT_TYPE_SONIFICATION");
329 break;
330 case QAudio::GameRole:
331 r = QLatin1String("USAGE_GAME");
332 break;
333 default:
334 return;
335 }
336
337 int type = 0; // CONTENT_TYPE_UNKNOWN
338 int usage = 0; // USAGE_UNKNOWN
339
340 if (r == QLatin1String("CONTENT_TYPE_MOVIE"))
341 type = 3;
342 else if (r == QLatin1String("CONTENT_TYPE_MUSIC"))
343 type = 2;
344 else if (r == QLatin1String("CONTENT_TYPE_SONIFICATION"))
345 type = 4;
346 else if (r == QLatin1String("CONTENT_TYPE_SPEECH"))
347 type = 1;
348 else if (r == QLatin1String("USAGE_ALARM"))
349 usage = 4;
350 else if (r == QLatin1String("USAGE_ASSISTANCE_ACCESSIBILITY"))
351 usage = 11;
352 else if (r == QLatin1String("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"))
353 usage = 12;
354 else if (r == QLatin1String("USAGE_ASSISTANCE_SONIFICATION"))
355 usage = 13;
356 else if (r == QLatin1String("USAGE_ASSISTANT"))
357 usage = 16;
358 else if (r == QLatin1String("USAGE_GAME"))
359 usage = 14;
360 else if (r == QLatin1String("USAGE_MEDIA"))
361 usage = 1;
362 else if (r == QLatin1String("USAGE_NOTIFICATION"))
363 usage = 5;
364 else if (r == QLatin1String("USAGE_NOTIFICATION_COMMUNICATION_DELAYED"))
365 usage = 9;
366 else if (r == QLatin1String("USAGE_NOTIFICATION_COMMUNICATION_INSTANT"))
367 usage = 8;
368 else if (r == QLatin1String("USAGE_NOTIFICATION_COMMUNICATION_REQUEST"))
369 usage = 7;
370 else if (r == QLatin1String("USAGE_NOTIFICATION_EVENT"))
371 usage = 10;
372 else if (r == QLatin1String("USAGE_NOTIFICATION_RINGTONE"))
373 usage = 6;
374 else if (r == QLatin1String("USAGE_VOICE_COMMUNICATION"))
375 usage = 2;
376 else if (r == QLatin1String("USAGE_VOICE_COMMUNICATION_SIGNALLING"))
377 usage = 3;
378
379 mMediaPlayer.callMethod<void>("setAudioAttributes", "(II)V", jint(type), jint(usage));
380}
381#endif
382
383static void onErrorNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id)
384{
385 Q_UNUSED(env);
386 Q_UNUSED(thiz);
387 QReadLocker locker(rwLock);
388 const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
389 if (Q_UNLIKELY(i == -1))
390 return;
391
392 Q_EMIT (*mediaPlayers)[i]->error(what, extra);
393}
394
395static void onBufferingUpdateNative(JNIEnv *env, jobject thiz, jint percent, jlong id)
396{
397 Q_UNUSED(env);
398 Q_UNUSED(thiz);
399 QReadLocker locker(rwLock);
400 const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
401 if (Q_UNLIKELY(i == -1))
402 return;
403
404 Q_EMIT (*mediaPlayers)[i]->bufferingChanged(percent);
405}
406
407static void onProgressUpdateNative(JNIEnv *env, jobject thiz, jint progress, jlong id)
408{
409 Q_UNUSED(env);
410 Q_UNUSED(thiz);
411 QReadLocker locker(rwLock);
412 const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
413 if (Q_UNLIKELY(i == -1))
414 return;
415
416 Q_EMIT (*mediaPlayers)[i]->progressChanged(progress);
417}
418
419static void onDurationChangedNative(JNIEnv *env, jobject thiz, jint duration, jlong id)
420{
421 Q_UNUSED(env);
422 Q_UNUSED(thiz);
423 QReadLocker locker(rwLock);
424 const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
425 if (Q_UNLIKELY(i == -1))
426 return;
427
428 Q_EMIT (*mediaPlayers)[i]->durationChanged(duration);
429}
430
431static void onInfoNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id)
432{
433 Q_UNUSED(env);
434 Q_UNUSED(thiz);
435 QReadLocker locker(rwLock);
436 const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
437 if (Q_UNLIKELY(i == -1))
438 return;
439
440 Q_EMIT (*mediaPlayers)[i]->info(what, extra);
441}
442
443static void onStateChangedNative(JNIEnv *env, jobject thiz, jint state, jlong id)
444{
445 Q_UNUSED(env);
446 Q_UNUSED(thiz);
447 QReadLocker locker(rwLock);
448 const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
449 if (Q_UNLIKELY(i == -1))
450 return;
451
452 Q_EMIT (*mediaPlayers)[i]->stateChanged(state);
453}
454
455static void onVideoSizeChangedNative(JNIEnv *env,
456 jobject thiz,
457 jint width,
458 jint height,
459 jlong id)
460{
461 Q_UNUSED(env);
462 Q_UNUSED(thiz);
463 QReadLocker locker(rwLock);
464 const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
465 if (Q_UNLIKELY(i == -1))
466 return;
467
468 Q_EMIT (*mediaPlayers)[i]->videoSizeChanged(width, height);
469}
470
472{
473 auto mediaplayer = reinterpret_cast<AndroidMediaPlayer *>(ptr);
474 if (!mediaplayer || !mediaPlayers->contains(mediaplayer))
475 return nullptr;
476
477 return mediaplayer;
478}
479
480static void onTrackInfoChangedNative(JNIEnv *env, jobject thiz, jlong ptr)
481{
482 Q_UNUSED(env);
483 Q_UNUSED(thiz);
484
485 QReadLocker locker(rwLock);
486 auto mediaplayer = getMediaPlayer(ptr);
487 if (!mediaplayer)
488 return;
489
490 emit mediaplayer->tracksInfoChanged();
491}
492
493static void onTimedTextChangedNative(JNIEnv *env, jobject thiz, jstring timedText, jint time,
494 jlong ptr)
495{
496 Q_UNUSED(env);
497 Q_UNUSED(thiz);
498 Q_UNUSED(time);
499
500 QReadLocker locker(rwLock);
501
502 auto mediaplayer = getMediaPlayer(ptr);
503 if (!mediaplayer)
504 return;
505
506 QString subtitleText;
507 if (timedText != nullptr)
508 subtitleText = QString::fromUtf8(env->GetStringUTFChars(timedText, 0));
509
510 emit mediaplayer->timedTextChanged(subtitleText);
511}
512
514{
515 static const JNINativeMethod methods[] = {
516 { "onErrorNative", "(IIJ)V", reinterpret_cast<void *>(onErrorNative) },
517 { "onBufferingUpdateNative", "(IJ)V", reinterpret_cast<void *>(onBufferingUpdateNative) },
518 { "onProgressUpdateNative", "(IJ)V", reinterpret_cast<void *>(onProgressUpdateNative) },
519 { "onDurationChangedNative", "(IJ)V", reinterpret_cast<void *>(onDurationChangedNative) },
520 { "onInfoNative", "(IIJ)V", reinterpret_cast<void *>(onInfoNative) },
521 { "onVideoSizeChangedNative", "(IIJ)V",
522 reinterpret_cast<void *>(onVideoSizeChangedNative) },
523 { "onStateChangedNative", "(IJ)V", reinterpret_cast<void *>(onStateChangedNative) },
524 { "onTrackInfoChangedNative", "(J)V", reinterpret_cast<void *>(onTrackInfoChangedNative) },
525 { "onTimedTextChangedNative", "(Ljava/lang/String;IJ)V",
526 reinterpret_cast<void *>(onTimedTextChangedNative) }
527 };
528
529 const int size = std::size(methods);
530 return QJniEnvironment().registerNativeMethods(QtAndroidMediaPlayerClassName, methods, size);
531}
532
533QT_END_NAMESPACE
534
535#include "moc_androidmediaplayer_p.cpp"
static void onErrorNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id)
static void onBufferingUpdateNative(JNIEnv *env, jobject thiz, jint percent, jlong id)
static void onTrackInfoChangedNative(JNIEnv *env, jobject thiz, jlong ptr)
static void onDurationChangedNative(JNIEnv *env, jobject thiz, jint duration, jlong id)
QList< AndroidMediaPlayer * > MediaPlayerList
static const char QtAndroidMediaPlayerClassName[]
static void onVideoSizeChangedNative(JNIEnv *env, jobject thiz, jint width, jint height, jlong id)
static void onInfoNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id)
AndroidMediaPlayer::TrackInfo convertTrackInfo(int streamNumber, QJniObject androidTrackInfo)
static AndroidMediaPlayer * getMediaPlayer(jlong ptr)
static void onStateChangedNative(JNIEnv *env, jobject thiz, jint state, jlong id)
static void onProgressUpdateNative(JNIEnv *env, jobject thiz, jint progress, jlong id)
static void onTimedTextChangedNative(JNIEnv *env, jobject thiz, jstring timedText, jint time, jlong ptr)
void seekTo(qint32 msec)
void deselectTrack(int trackNumber)
static bool registerNativeMethods()
void selectTrack(int trackNumber)
QList< TrackInfo > tracksInfo()
void setDataSource(const QNetworkRequest &request)
static void stopSoundStreaming()
void setDisplay(AndroidSurfaceTexture *surfaceTexture)
bool setPlaybackRate(qreal rate)
int activeTrack(TrackType trackType)
static void startSoundStreaming(const int inputId, const int outputId)
void setVolume(int volume)
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)