summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamuel Mira <samuel.mira@qt.io>2021-08-16 15:51:35 +0300
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-08-23 10:09:19 +0000
commit5e755001df414b16f8cec9c4785060ee703e3680 (patch)
tree7d9a2eca30799d97ca4ef11ea82611a00251695f
parent00a6e970c30cd3298e45020a7e4eb5bd60d65170 (diff)
Android : Track and set different audio/video/subtitle tracks
Implemented trackCount,trackMetadata,activeTrack and setActiveTrack in qandroidmediaplayer. The functionality itself was implemented in QtAndroidMediaPlayer, so the JNI wrapper had to be changed. The two types of android subtitles (subtitles and timedtext) are merged as only one in Qt. Change-Id: Ic22a248f82a40eff809319e67dd870c5dcbc37d1 Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io> (cherry picked from commit 2301ae72f8040e5f12e5c6605d52cc5f9fd29016) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java193
-rw-r--r--src/multimedia/platform/android/mediaplayer/qandroidmediaplayer.cpp167
-rw-r--r--src/multimedia/platform/android/mediaplayer/qandroidmediaplayer_p.h11
-rw-r--r--src/multimedia/platform/android/mediaplayer/qandroidmetadata.cpp38
-rw-r--r--src/multimedia/platform/android/mediaplayer/qandroidmetadata_p.h12
-rw-r--r--src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp140
-rw-r--r--src/multimedia/platform/android/wrappers/jni/androidmediaplayer_p.h20
7 files changed, 532 insertions, 49 deletions
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java
index 72a299c7d..fb1ba3088 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java
@@ -47,7 +47,9 @@ import java.io.FileInputStream;
// API is level is < 9 unless marked otherwise.
import android.content.Context;
import android.media.MediaPlayer;
+import android.media.MediaFormat;
import android.media.AudioAttributes;
+import android.media.TimedText;
import android.net.Uri;
import android.util.Log;
import java.io.FileDescriptor;
@@ -66,6 +68,9 @@ public class QtAndroidMediaPlayer
native public void onVideoSizeChangedNative(int width, int height, long id);
native public void onStateChangedNative(int state, long id);
+ native public void onTrackInfoChangedNative(long id);
+ native public void onTimedTextChangedNative(String text, int time, long id);
+
private MediaPlayer mMediaPlayer = null;
private AudioAttributes mAudioAttributes = null;
private HashMap<String, String> mHeaders = null;
@@ -90,6 +95,23 @@ public class QtAndroidMediaPlayer
final static int Error = 0x200;
}
+ public class TrackInfo
+ {
+ private int type;
+ private String mime, language;
+
+ TrackInfo(int type, String mime, String language)
+ {
+ this.type = type;
+ this.mime = mime;
+ this.language = language;
+ }
+
+ int getType() { return this.type; }
+ String getMime() { return this.mime; }
+ String getLanguage() { return this.language; }
+ }
+
private volatile int mState = State.Uninitialized;
/**
@@ -107,7 +129,6 @@ public class QtAndroidMediaPlayer
onErrorNative(what, extra, mID);
return true;
}
-
}
/**
@@ -174,6 +195,7 @@ public class QtAndroidMediaPlayer
{
setState(State.Prepared);
onDurationChangedNative(getDuration(), mID);
+ onTrackInfoChangedNative(mID);
}
}
@@ -210,6 +232,14 @@ public class QtAndroidMediaPlayer
}
+ private class MediaPlayerTimedTextListener implements MediaPlayer.OnTimedTextListener
+ {
+ @Override public void onTimedText(MediaPlayer mp, TimedText text)
+ {
+ onTimedTextChangedNative(text.getText(), mp.getCurrentPosition(), mID);
+ }
+ }
+
public QtAndroidMediaPlayer(final Context context, final long id)
{
mID = id;
@@ -231,7 +261,6 @@ public class QtAndroidMediaPlayer
onStateChangedNative(mState, mID);
}
-
private void init()
{
if (mMediaPlayer == null) {
@@ -255,8 +284,8 @@ public class QtAndroidMediaPlayer
try {
mMediaPlayer.start();
setState(State.Started);
- } catch (final IllegalStateException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
}
}
@@ -269,8 +298,8 @@ public class QtAndroidMediaPlayer
try {
mMediaPlayer.pause();
setState(State.Paused);
- } catch (final IllegalStateException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
}
}
@@ -288,8 +317,8 @@ public class QtAndroidMediaPlayer
try {
mMediaPlayer.stop();
setState(State.Stopped);
- } catch (final IllegalStateException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
}
}
@@ -305,8 +334,8 @@ public class QtAndroidMediaPlayer
try {
mMediaPlayer.seekTo(msec);
- } catch (final IllegalStateException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
}
}
@@ -326,8 +355,8 @@ public class QtAndroidMediaPlayer
try {
playing = mMediaPlayer.isPlaying();
- } catch (final IllegalStateException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
}
return playing;
@@ -341,8 +370,8 @@ public class QtAndroidMediaPlayer
try {
mMediaPlayer.prepareAsync();
setState(State.Preparing);
- } catch (final IllegalStateException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
}
}
@@ -371,6 +400,7 @@ public class QtAndroidMediaPlayer
mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayerVideoSizeChangedListener());
mMediaPlayer.setOnErrorListener(new MediaPlayerErrorListener());
mMediaPlayer.setOnPreparedListener(new MediaPlayerPreparedListener());
+ mMediaPlayer.setOnTimedTextListener(new MediaPlayerTimedTextListener());
if (mSurfaceHolder != null)
mMediaPlayer.setDisplay(mSurfaceHolder);
@@ -397,8 +427,8 @@ public class QtAndroidMediaPlayer
mMediaPlayer.setDataSource(path);
}
setState(State.Initialized);
- } catch (final Exception e) {
- Log.d(TAG, "Exception: " + e.getMessage());
+ } catch (final Exception exception) {
+ Log.w(TAG, exception);
} finally {
try {
if (afd != null)
@@ -417,6 +447,121 @@ public class QtAndroidMediaPlayer
}
}
+ private boolean isMediaPlayerPrepared()
+ {
+ int preparedState = (State.Prepared | State.Started | State.Paused | State.Stopped
+ | State.PlaybackCompleted);
+ return ((mState & preparedState) != 0);
+ }
+
+ public TrackInfo[] getAllTrackInfo()
+ {
+ if (!isMediaPlayerPrepared()) {
+ Log.w(TAG, "Trying to get track info of a media player that is not prepared!");
+ return new TrackInfo[0];
+ }
+
+ MediaPlayer.TrackInfo[] tracks = new MediaPlayer.TrackInfo[0];
+
+ try {
+ // media player will ignore if this a out bounds index.
+ tracks = mMediaPlayer.getTrackInfo();
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
+ }
+
+ int numberOfTracks = tracks.length;
+ TrackInfo[] qtTracksInfo = new TrackInfo[numberOfTracks];
+
+ for (int index = 0; index < numberOfTracks; index++) {
+
+ MediaPlayer.TrackInfo track = tracks[index];
+
+ int type = track.getTrackType();
+ String mimeType = getMimeType(track);
+ String language = track.getLanguage();
+
+ qtTracksInfo[index] = new TrackInfo(type, mimeType, language);
+ }
+
+ return qtTracksInfo;
+ }
+
+ private String getMimeType(MediaPlayer.TrackInfo trackInfo)
+ {
+ // The "octet-stream" subtype is used to indicate that a body contains arbitrary binary
+ // data.
+ String defaultMimeType = "application/octet-stream";
+
+ String mimeType = defaultMimeType;
+
+ MediaFormat mediaFormat = trackInfo.getFormat();
+ if (mediaFormat != null) {
+ mimeType = mediaFormat.getString(MediaFormat.KEY_MIME, defaultMimeType);
+ }
+
+ return mimeType;
+ }
+
+ public void selectTrack(int index)
+ {
+ if (!isMediaPlayerPrepared()) {
+ Log.d(TAG, "Trying to select a track of a media player that is not prepared!");
+ return;
+ }
+ try {
+ // media player will ignore if this a out bounds index.
+ mMediaPlayer.selectTrack(index);
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
+ }
+ }
+
+ public void deselectTrack(int index)
+ {
+ if (!isMediaPlayerPrepared()) {
+ Log.d(TAG, "Trying to deselect track of a media player that is not prepared!");
+ return;
+ }
+
+ try {
+ // media player will ignore if this a out bounds index.
+ mMediaPlayer.deselectTrack(index);
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
+ }
+ }
+
+ public int getSelectedTrack(int type)
+ {
+
+ int InvalidTrack = -1;
+ if (!isMediaPlayerPrepared()) {
+ Log.d(TAG, "Trying to get the selected track of a media player that is not prepared!");
+ return InvalidTrack;
+ }
+
+ boolean isVideoTrackType = (type == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO);
+ boolean isAudioTrackType = (type == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO);
+ boolean isTimedTextTrackType = (type == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT);
+ boolean isSubtitleTrackType = (type == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
+
+ if (!(isVideoTrackType || isAudioTrackType || isSubtitleTrackType
+ || isTimedTextTrackType)) {
+ Log.w(TAG,
+ "Trying to get a selected track of a invalid type"
+ + " Only Video,Audio, TimedText and Subtitle tracks are selectable.");
+ return InvalidTrack;
+ }
+
+ try {
+ return mMediaPlayer.getSelectedTrack(type);
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
+ }
+
+ return InvalidTrack;
+ }
public int getCurrentPosition()
{
@@ -433,8 +578,8 @@ public class QtAndroidMediaPlayer
try {
currentPosition = mMediaPlayer.getCurrentPosition();
- } catch (final IllegalStateException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
}
return currentPosition;
@@ -454,8 +599,8 @@ public class QtAndroidMediaPlayer
try {
duration = mMediaPlayer.getDuration();
- } catch (final IllegalStateException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
}
return duration;
@@ -490,8 +635,8 @@ public class QtAndroidMediaPlayer
try {
float newVolume = (float)volume / 100;
mMediaPlayer.setVolume(newVolume, newVolume);
- } catch (final IllegalStateException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalStateException exception) {
+ Log.w(TAG, exception);
}
}
@@ -573,8 +718,8 @@ public class QtAndroidMediaPlayer
try {
player.setAudioAttributes(attr);
- } catch (final IllegalArgumentException e) {
- Log.d(TAG, "" + e.getMessage());
+ } catch (final IllegalArgumentException exception) {
+ Log.w(TAG, exception);
}
}
}
diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer.cpp b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer.cpp
index 1cfa88f1c..2f1d04487 100644
--- a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer.cpp
+++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer.cpp
@@ -75,26 +75,27 @@ private:
QMediaPlayer::MediaStatus mPreviousMediaStatus;
};
-
QAndroidMediaPlayer::QAndroidMediaPlayer(QMediaPlayer *parent)
: QPlatformMediaPlayer(parent),
mMediaPlayer(new AndroidMediaPlayer),
mState(AndroidMediaPlayer::Uninitialized)
{
- connect(mMediaPlayer, &AndroidMediaPlayer::bufferingChanged,
- this, &QAndroidMediaPlayer::onBufferingChanged);
- connect(mMediaPlayer, &AndroidMediaPlayer::info,
- this, &QAndroidMediaPlayer::onInfo);
- connect(mMediaPlayer, &AndroidMediaPlayer::error,
- this, &QAndroidMediaPlayer::onError);
- connect(mMediaPlayer, &AndroidMediaPlayer::stateChanged,
- this, &QAndroidMediaPlayer::onStateChanged);
- connect(mMediaPlayer, &AndroidMediaPlayer::videoSizeChanged,
- this, &QAndroidMediaPlayer::onVideoSizeChanged);
- connect(mMediaPlayer, &AndroidMediaPlayer::progressChanged,
- this, &QAndroidMediaPlayer::positionChanged);
- connect(mMediaPlayer, &AndroidMediaPlayer::durationChanged,
- this, &QAndroidMediaPlayer::durationChanged);
+ connect(mMediaPlayer, &AndroidMediaPlayer::bufferingChanged, this,
+ &QAndroidMediaPlayer::onBufferingChanged);
+ connect(mMediaPlayer, &AndroidMediaPlayer::info, this, &QAndroidMediaPlayer::onInfo);
+ connect(mMediaPlayer, &AndroidMediaPlayer::error, this, &QAndroidMediaPlayer::onError);
+ connect(mMediaPlayer, &AndroidMediaPlayer::stateChanged, this,
+ &QAndroidMediaPlayer::onStateChanged);
+ connect(mMediaPlayer, &AndroidMediaPlayer::videoSizeChanged, this,
+ &QAndroidMediaPlayer::onVideoSizeChanged);
+ connect(mMediaPlayer, &AndroidMediaPlayer::progressChanged, this,
+ &QAndroidMediaPlayer::positionChanged);
+ connect(mMediaPlayer, &AndroidMediaPlayer::durationChanged, this,
+ &QAndroidMediaPlayer::durationChanged);
+ connect(mMediaPlayer, &AndroidMediaPlayer::tracksInfoChanged, this,
+ &QAndroidMediaPlayer::updateTrackInfo);
+ connect(mMediaPlayer, &AndroidMediaPlayer::timedTextChanged, this,
+ &QAndroidMediaPlayer::setSubtitle);
}
QAndroidMediaPlayer::~QAndroidMediaPlayer()
@@ -638,6 +639,108 @@ void QAndroidMediaPlayer::onStateChanged(qint32 state)
}
}
+int QAndroidMediaPlayer::trackCount(TrackType trackType)
+{
+ if (!mTracksMetadata.contains(trackType))
+ return -1;
+
+ auto tracks = mTracksMetadata.value(trackType);
+ return tracks.count();
+}
+
+QMediaMetaData QAndroidMediaPlayer::trackMetaData(TrackType trackType, int streamNumber)
+{
+ if (!mTracksMetadata.contains(trackType))
+ return QMediaMetaData();
+
+ auto tracks = mTracksMetadata.value(trackType);
+ if (tracks.count() < streamNumber)
+ return QMediaMetaData();
+
+ QAndroidMetaData trackInfo = tracks.at(streamNumber);
+ return static_cast<QMediaMetaData>(trackInfo);
+}
+
+QPlatformMediaPlayer::TrackType convertTrackType(AndroidMediaPlayer::TrackType type)
+{
+ switch (type) {
+ case AndroidMediaPlayer::TrackType::Video:
+ return QPlatformMediaPlayer::TrackType::VideoStream;
+ case AndroidMediaPlayer::TrackType::Audio:
+ return QPlatformMediaPlayer::TrackType::AudioStream;
+ case AndroidMediaPlayer::TrackType::TimedText:
+ return QPlatformMediaPlayer::TrackType::SubtitleStream;
+ case AndroidMediaPlayer::TrackType::Subtitle:
+ return QPlatformMediaPlayer::TrackType::SubtitleStream;
+ case AndroidMediaPlayer::TrackType::Unknown:
+ case AndroidMediaPlayer::TrackType::Metadata:
+ return QPlatformMediaPlayer::TrackType::NTrackTypes;
+ }
+
+ return QPlatformMediaPlayer::TrackType::NTrackTypes;
+}
+
+int QAndroidMediaPlayer::activeTrack(TrackType trackType)
+{
+ switch (trackType) {
+ case QPlatformMediaPlayer::TrackType::VideoStream:
+ return mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Video);
+ case QPlatformMediaPlayer::TrackType::AudioStream:
+ return mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Audio);
+ case QPlatformMediaPlayer::TrackType::SubtitleStream: {
+ int timedTextSelectedTrack =
+ mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::TimedText);
+ if (timedTextSelectedTrack > -1)
+ return timedTextSelectedTrack;
+
+ int subtitleSelectedTrack =
+ mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Subtitle);
+ if (subtitleSelectedTrack > -1)
+ return subtitleSelectedTrack;
+
+ return -1;
+ }
+ case QPlatformMediaPlayer::TrackType::NTrackTypes:
+ return -1;
+ }
+
+ return -1;
+}
+
+void QAndroidMediaPlayer::setActiveTrack(TrackType trackType, int streamNumber)
+{
+ if (!mTracksMetadata.contains(trackType)) {
+ qDebug() << "Trying to set a active track of a type that does not exist";
+ return;
+ }
+
+ const auto &tracks = mTracksMetadata.value(trackType);
+ if (streamNumber > tracks.count()) {
+ return;
+ }
+
+ if (trackType == TrackType::SubtitleStream) {
+ // subtitles and timedtext tracks can be selected at the same time so deselect both before
+ // selection
+ int subtitleSelectedTrack =
+ mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Subtitle);
+ if (subtitleSelectedTrack > -1)
+ mMediaPlayer->deselectTrack(subtitleSelectedTrack);
+
+ int timedTextSelectedTrack =
+ mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::TimedText);
+ if (timedTextSelectedTrack > -1)
+ mMediaPlayer->deselectTrack(timedTextSelectedTrack);
+
+ // in case of < 0 just deselect all - no subtitle selected.
+ if (streamNumber < 0)
+ return;
+ }
+
+ auto trackInfo = tracks.at(streamNumber);
+ mMediaPlayer->selectTrack(trackInfo.androidTrackNumber());
+}
+
void QAndroidMediaPlayer::positionChanged(qint64 position)
{
QPlatformMediaPlayer::positionChanged(position);
@@ -734,7 +837,7 @@ void QAndroidMediaPlayer::flushPendingStates()
void QAndroidMediaPlayer::updateBufferStatus()
{
- auto status = mediaStatus();
+ const auto &status = mediaStatus();
bool bufferFilled = (status == QMediaPlayer::BufferedMedia || status == QMediaPlayer::BufferingMedia);
if (mBufferFilled != bufferFilled) {
@@ -743,4 +846,36 @@ void QAndroidMediaPlayer::updateBufferStatus()
}
}
+void QAndroidMediaPlayer::updateTrackInfo()
+{
+ const auto &androidTracksInfo = mMediaPlayer->tracksInfo();
+
+ // prepare mTracksMetadata
+ mTracksMetadata[TrackType::VideoStream] = QList<QAndroidMetaData>();
+ mTracksMetadata[TrackType::AudioStream] = QList<QAndroidMetaData>();
+ mTracksMetadata[TrackType::SubtitleStream] = QList<QAndroidMetaData>();
+ mTracksMetadata[TrackType::NTrackTypes] = QList<QAndroidMetaData>();
+
+ for (const auto &androidTrackInfo : androidTracksInfo) {
+
+ const auto &mediaPlayerType = convertTrackType(androidTrackInfo.trackType);
+ auto &tracks = mTracksMetadata[mediaPlayerType];
+
+ const QAndroidMetaData metadata(mediaPlayerType, androidTrackInfo.trackType,
+ androidTrackInfo.trackNumber, androidTrackInfo.mimeType,
+ androidTrackInfo.language);
+ tracks.append(metadata);
+ }
+
+ emit tracksChanged();
+}
+
+void QAndroidMediaPlayer::setSubtitle(QString subtitle)
+{
+ if (mSubtitle == subtitle)
+ return;
+
+ mSubtitle = subtitle;
+}
+
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer_p.h b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer_p.h
index c134e5b08..c3034c18f 100644
--- a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer_p.h
+++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer_p.h
@@ -53,6 +53,7 @@
#include <qglobal.h>
#include <private/qplatformmediaplayer_p.h>
+#include <private/qandroidmetadata_p.h>
#include <qsize.h>
#include <qurl.h>
@@ -66,6 +67,7 @@ class QAndroidAudioOutput;
class QAndroidMediaPlayer : public QObject, public QPlatformMediaPlayer
{
Q_OBJECT
+
public:
explicit QAndroidMediaPlayer(QMediaPlayer *parent = 0);
~QAndroidMediaPlayer() override;
@@ -94,6 +96,11 @@ public:
void pause() override;
void stop() override;
+ int trackCount(TrackType trackType) override;
+ QMediaMetaData trackMetaData(TrackType trackType, int streamNumber) override;
+ int activeTrack(TrackType trackType) override;
+ void setActiveTrack(TrackType trackType, int streamNumber) override;
+
private Q_SLOTS:
void setVolume(float volume);
void setMuted(bool muted);
@@ -130,6 +137,8 @@ private:
int mActiveStateChangeNotifiers = 0;
qreal mPendingPlaybackRate = 1.;
bool mHasPendingPlaybackRate = false; // we need this because the rate can theoretically be negative
+ QMap<TrackType, QList<QAndroidMetaData>> mTracksMetadata;
+ QString mSubtitle;
void setMediaStatus(QMediaPlayer::MediaStatus status);
void setAudioAvailable(bool available);
@@ -138,6 +147,8 @@ private:
void resetBufferingProgress();
void flushPendingStates();
void updateBufferStatus();
+ void updateTrackInfo();
+ void setSubtitle(QString subtitle);
friend class StateChangeNotifier;
};
diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmetadata.cpp b/src/multimedia/platform/android/mediaplayer/qandroidmetadata.cpp
index 5fd3a91f4..8f7d39efa 100644
--- a/src/multimedia/platform/android/mediaplayer/qandroidmetadata.cpp
+++ b/src/multimedia/platform/android/mediaplayer/qandroidmetadata.cpp
@@ -161,4 +161,42 @@ QMediaMetaData QAndroidMetaData::extractMetadata(const QUrl &url)
return metadata;
}
+QLocale::Language getLocaleLanguage(const QString &language)
+{
+ // undefined language or uncoded language
+ if (language == QLatin1String("und") || language == QStringLiteral("mis"))
+ return QLocale::AnyLanguage;
+
+ QLocale locale(language);
+ if (locale != QLocale::c())
+ return locale.language();
+
+ return QLocale::codeToLanguage(language.left(2));
+}
+
+QAndroidMetaData::QAndroidMetaData(int trackType, int androidTrackType, int androidTrackNumber,
+ const QString &mimeType, const QString &language)
+ : mTrackType(trackType),
+ mAndroidTrackType(androidTrackType),
+ mAndroidTrackNumber(androidTrackNumber)
+{
+ insert(QMediaMetaData::MediaType, mimeType);
+ insert(QMediaMetaData::Language, getLocaleLanguage(language));
+}
+
+int QAndroidMetaData::trackType()
+{
+ return mTrackType;
+}
+
+int QAndroidMetaData::androidTrackType()
+{
+ return mAndroidTrackType;
+}
+
+int QAndroidMetaData::androidTrackNumber()
+{
+ return mAndroidTrackNumber;
+}
+
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmetadata_p.h b/src/multimedia/platform/android/mediaplayer/qandroidmetadata_p.h
index 4ce65dc09..2346806bb 100644
--- a/src/multimedia/platform/android/mediaplayer/qandroidmetadata_p.h
+++ b/src/multimedia/platform/android/mediaplayer/qandroidmetadata_p.h
@@ -64,6 +64,18 @@ class QAndroidMetaData : public QMediaMetaData
{
public:
static QMediaMetaData extractMetadata(const QUrl &url);
+
+ QAndroidMetaData(int trackType, int androidTrackType, int androidTrackNumber,
+ const QString &mimeType, const QString &language);
+
+ int trackType();
+ int androidTrackType();
+ int androidTrackNumber();
+
+private:
+ int mTrackType;
+ int mAndroidTrackType;
+ int mAndroidTrackNumber;
};
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp
index 84ec3edbd..b4b886329 100644
--- a/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp
+++ b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp
@@ -148,6 +148,86 @@ 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 };
+
+ auto type = androidTrackInfo.callMethod<jint>("getType", "()I");
+ if (exceptionCheckAndClear())
+ 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 (exceptionCheckAndClear()) {
+ 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");
@@ -427,16 +507,62 @@ static void onVideoSizeChangedNative(JNIEnv *env,
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;
+
+ 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));
+
+ emit mediaplayer->timedTextChanged(subtitleText);
+}
+
bool AndroidMediaPlayer::registerNativeMethods()
{
static 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)}
+ { "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 = sizeof(methods) / sizeof(methods[0]);
diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediaplayer_p.h b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer_p.h
index 96c93223c..5539a709f 100644
--- a/src/multimedia/platform/android/wrappers/jni/androidmediaplayer_p.h
+++ b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer_p.h
@@ -94,8 +94,7 @@ public:
MEDIA_INFO_METADATA_UPDATE = 802
};
- enum MediaPlayerState
- {
+ enum MediaPlayerState {
Uninitialized = 0x1, /* End */
Idle = 0x2,
Preparing = 0x4,
@@ -108,6 +107,16 @@ public:
Error = 0x200
};
+ enum TrackType { Unknown = 0, Video, Audio, TimedText, Subtitle, Metadata };
+
+ struct TrackInfo
+ {
+ int trackNumber;
+ TrackType trackType;
+ QString language;
+ QString mimeType;
+ };
+
void release();
void reset();
@@ -130,6 +139,11 @@ public:
bool setPlaybackRate(qreal rate);
void setDisplay(AndroidSurfaceTexture *surfaceTexture);
static bool setAudioOutput(const QByteArray &deviceId);
+ QList<TrackInfo> tracksInfo();
+ int activeTrack(TrackType trackType);
+ void deselectTrack(int trackNumber);
+ void selectTrack(int trackNumber);
+
static bool registerNativeMethods();
Q_SIGNALS:
@@ -140,6 +154,8 @@ Q_SIGNALS:
void stateChanged(qint32 state);
void info(qint32 what, qint32 extra);
void videoSizeChanged(qint32 width, qint32 height);
+ void timedTextChanged(QString text);
+ void tracksInfoChanged();
private:
QJniObject mMediaPlayer;