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
QtAndroidMediaPlayer.java
Go to the documentation of this file.
1// Copyright (C) 2016 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
4package org.qtproject.qt.android.multimedia;
5
6import java.io.IOException;
7import java.lang.String;
8import java.util.HashMap;
9import java.io.FileInputStream;
10
11// API is level is < 9 unless marked otherwise.
12import android.content.Context;
13import android.media.MediaPlayer;
14import android.media.MediaFormat;
15import android.media.PlaybackParams;
16import android.media.AudioAttributes;
17import android.media.TimedText;
18import android.net.Uri;
19import android.util.Log;
20import java.io.FileDescriptor;
21import android.content.res.AssetFileDescriptor;
22import android.content.res.AssetManager;
23import android.view.SurfaceHolder;
24
25import java.util.concurrent.Executors;
26import java.util.concurrent.ScheduledExecutorService;
27import java.util.concurrent.ScheduledFuture;
28import java.util.concurrent.TimeUnit;
29
30class QtAndroidMediaPlayer
31{
32 // Native callback functions for MediaPlayer
33 native void onErrorNative(int what, int extra, long id);
34 native void onBufferingUpdateNative(int percent, long id);
35 native void onProgressUpdateNative(int progress, long id);
36 native void onDurationChangedNative(int duration, long id);
37 native void onInfoNative(int what, int extra, long id);
38 native void onVideoSizeChangedNative(int width, int height, long id);
39 native void onStateChangedNative(int state, long id);
40
41 native void onTrackInfoChangedNative(long id);
42 native void onTimedTextChangedNative(String text, int time, long id);
43
44 private MediaPlayer mMediaPlayer = null;
45 private AudioAttributes mAudioAttributes = null;
46 private HashMap<String, String> mHeaders = null;
47 private Uri mUri = null;
48 private final long mID;
49 private final Context mContext;
50 private boolean mMuted = false;
51 private int mVolume = 100;
52 private static final String TAG = "Qt MediaPlayer";
53 private SurfaceHolder mSurfaceHolder = null;
54 private ScheduledExecutorService mProgressScheduler = null;
55 private ScheduledFuture<?> mProgressWatcherHandle = null;
56
57 private class State {
58 final static int Uninitialized = 0x1 /* End */;
59 final static int Idle = 0x2;
60 final static int Preparing = 0x4;
61 final static int Prepared = 0x8;
62 final static int Initialized = 0x10;
63 final static int Started = 0x20;
64 final static int Stopped = 0x40;
65 final static int Paused = 0x80;
66 final static int PlaybackCompleted = 0x100;
67 final static int Error = 0x200;
68 }
69
70 class TrackInfo
71 {
72 private int type;
73 private String mime, language;
74
75 TrackInfo(int type, String mime, String language)
76 {
77 this.type = type;
78 this.mime = mime;
79 this.language = language;
80 }
81
82 int getType() { return this.type; }
83 String getMime() { return this.mime; }
84 String getLanguage() { return this.language; }
85 }
86
87 private volatile int mState = State.Uninitialized;
88
92 private class MediaPlayerErrorListener
93 implements MediaPlayer.OnErrorListener
94 {
95 @Override
96 public boolean onError(final MediaPlayer mp,
97 final int what,
98 final int extra)
99 {
100 setState(State.Error);
101 onErrorNative(what, extra, mID);
102 return true;
103 }
104 }
105
109 private class MediaPlayerBufferingListener
110 implements MediaPlayer.OnBufferingUpdateListener
111 {
112 private int mBufferPercent = -1;
113 @Override
114 public void onBufferingUpdate(final android.media.MediaPlayer mp,
115 final int percent)
116 {
117 // Avoid updates when percent is unchanged.
118 // E.g., we keep getting updates when percent == 100
119 if (mBufferPercent == percent)
120 return;
121
122 onBufferingUpdateNative((mBufferPercent = percent), mID);
123 }
124
125 }
126
130 private class MediaPlayerCompletionListener
131 implements MediaPlayer.OnCompletionListener
132 {
133 @Override
134 public void onCompletion(final MediaPlayer mp)
135 {
136 setState(State.PlaybackCompleted);
137 }
138
139 }
140
144 private class MediaPlayerInfoListener
145 implements MediaPlayer.OnInfoListener
146 {
147 @Override
148 public boolean onInfo(final MediaPlayer mp,
149 final int what,
150 final int extra)
151 {
152 onInfoNative(what, extra, mID);
153 return true;
154 }
155
156 }
157
161 private class MediaPlayerPreparedListener
162 implements MediaPlayer.OnPreparedListener
163 {
164
165 @Override
166 public void onPrepared(final MediaPlayer mp)
167 {
168 setState(State.Prepared);
169 onDurationChangedNative(getDuration(), mID);
171 }
172
173 }
174
178 private class MediaPlayerSeekCompleteListener
179 implements MediaPlayer.OnSeekCompleteListener
180 {
181
182 @Override
183 public void onSeekComplete(final MediaPlayer mp)
184 {
185 onProgressUpdateNative(getCurrentPosition(), mID);
186 }
187
188 }
189
193 private class MediaPlayerVideoSizeChangedListener
194 implements MediaPlayer.OnVideoSizeChangedListener
195 {
196
197 @Override
198 public void onVideoSizeChanged(final MediaPlayer mp,
199 final int width,
200 final int height)
201 {
203 }
204
205 }
206
207 private class MediaPlayerTimedTextListener implements MediaPlayer.OnTimedTextListener
208 {
209 @Override
210 public void onTimedText(MediaPlayer mp, TimedText text)
211 {
212 onTimedTextChangedNative(text.getText(), mp.getCurrentPosition(), mID);
213 }
214 }
215
216 QtAndroidMediaPlayer(final Context context, final long id)
217 {
218 mID = id;
219 mContext = context;
220 }
221
222 MediaPlayer getMediaPlayerHandle()
223 {
224 return mMediaPlayer;
225 }
226
227 private void setState(int state)
228 {
229 if (mState == state)
230 return;
231
232 mState = state;
233
234 onStateChangedNative(mState, mID);
235 }
236
237 private void init()
238 {
239 if (mMediaPlayer != null)
240 return;
241
242 mMediaPlayer = new MediaPlayer();
243 setState(State.Idle);
244 // Make sure the new media player has the volume that was set on the QMediaPlayer
245 setVolumeHelper(mMuted ? 0 : mVolume);
246 setAudioAttributes(mMediaPlayer, mAudioAttributes);
247
248 mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayerBufferingListener());
249 mMediaPlayer.setOnCompletionListener(new MediaPlayerCompletionListener());
250 mMediaPlayer.setOnInfoListener(new MediaPlayerInfoListener());
251 mMediaPlayer.setOnSeekCompleteListener(new MediaPlayerSeekCompleteListener());
252 mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayerVideoSizeChangedListener());
253 mMediaPlayer.setOnErrorListener(new MediaPlayerErrorListener());
254 mMediaPlayer.setOnPreparedListener(new MediaPlayerPreparedListener());
255 mMediaPlayer.setOnTimedTextListener(new MediaPlayerTimedTextListener());
256 // Report playback position since there is no default listner for that in MediaPlayer
257 mProgressScheduler = Executors.newScheduledThreadPool(1);
258 }
259
260 void startProgressWatcher()
261 {
262 // if it was shutdown, request new thread
263 if (mProgressScheduler.isTerminated() || mProgressScheduler == null)
264 mProgressScheduler = Executors.newScheduledThreadPool(1);
265
266 mProgressWatcherHandle = mProgressScheduler.scheduleAtFixedRate(new Runnable() {
267 @Override
268 public void run() {
269 if (isPlaying())
270 onProgressUpdateNative(getCurrentPosition(), mID);
271 }
272 }, 10, 100, TimeUnit.MILLISECONDS);
273 }
274
275 void cancelProgressWatcher()
276 {
277 if (mProgressScheduler != null)
278 mProgressScheduler.shutdown();
279 }
280
281 void start()
282 {
283 if ((mState & (State.Prepared
284 | State.Started
285 | State.Paused
286 | State.PlaybackCompleted)) == 0) {
287 return;
288 }
289
290 try {
291 mMediaPlayer.start();
292 setState(State.Started);
293 startProgressWatcher();
294 } catch (final IllegalStateException exception) {
295 Log.w(TAG, exception);
296 }
297 }
298
299 void pause()
300 {
301 if ((mState & (State.Started | State.Paused | State.PlaybackCompleted)) == 0)
302 return;
303
304 try {
305 mMediaPlayer.pause();
306 setState(State.Paused);
307 } catch (final IllegalStateException exception) {
308 Log.w(TAG, exception);
309 }
310 }
311
312
313 void stop()
314 {
315 if ((mState & (State.Prepared
316 | State.Started
317 | State.Stopped
318 | State.Paused
319 | State.PlaybackCompleted)) == 0) {
320 return;
321 }
322
323 try {
324 mMediaPlayer.stop();
325 setState(State.Stopped);
326 cancelProgressWatcher();
327 } catch (final IllegalStateException exception) {
328 Log.w(TAG, exception);
329 }
330 }
331
332
333 void seekTo(final int msec)
334 {
335 if ((mState & (State.Prepared
336 | State.Started
337 | State.Paused
338 | State.PlaybackCompleted)) == 0) {
339 return;
340 }
341
342 try {
343 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
344 // seekTo to closest frame of the provided msec is only available for devices
345 // with api level over 26
346 mMediaPlayer.seekTo(msec, MediaPlayer.SEEK_CLOSEST);
347 } else {
348 mMediaPlayer.seekTo(msec);
349 }
350 } catch (final IllegalStateException exception) {
351 Log.w(TAG, exception);
352 }
353 }
354
355 boolean isPlaying()
356 {
357 boolean playing = false;
358 if ((mState & (State.Idle
359 | State.Initialized
360 | State.Prepared
361 | State.Started
362 | State.Paused
363 | State.Stopped
364 | State.PlaybackCompleted)) == 0) {
365 return playing;
366 }
367
368 try {
369 playing = mMediaPlayer.isPlaying();
370 } catch (final IllegalStateException exception) {
371 Log.w(TAG, exception);
372 }
373
374 return playing;
375 }
376
377 void prepareAsync()
378 {
379 if ((mState & (State.Initialized | State.Stopped)) == 0)
380 return;
381
382 try {
383 mMediaPlayer.prepareAsync();
384 setState(State.Preparing);
385 } catch (final IllegalStateException exception) {
386 Log.w(TAG, exception);
387 }
388 }
389
390 void initHeaders()
391 {
392 mHeaders = new HashMap<String, String>();
393 }
394
395 void setHeader(final String header, final String value)
396 {
397 mHeaders.put(header, value);
398 }
399
400 void setDataSource(final String path)
401 {
402 if (mState == State.Uninitialized)
403 init();
404
405 if (mState != State.Idle)
406 reset();
407
408 // mediaplayer can only setDataSource if it is on State.Idle
409 if (mState != State.Idle) {
410 Log.w(TAG, "Trying to set data source of a media player that is not idle!");
411 return;
412 }
413
414 if (mSurfaceHolder != null)
415 mMediaPlayer.setDisplay(mSurfaceHolder);
416
417 AssetFileDescriptor afd = null;
418 FileInputStream fis = null;
419 try {
420 mUri = Uri.parse(path);
421 if (mUri.getScheme().compareTo("assets") == 0) {
422 final String asset = mUri.getPath().substring(1 /* Remove first '/' */);
423 final AssetManager am = mContext.getAssets();
424 afd = am.openFd(asset);
425 final long offset = afd.getStartOffset();
426 final long length = afd.getLength();
427 FileDescriptor fd = afd.getFileDescriptor();
428 mMediaPlayer.setDataSource(fd, offset, length);
429 } else if (mUri.getScheme().compareTo("file") == 0) {
430 fis = new FileInputStream(mUri.getPath());
431 FileDescriptor fd = fis.getFD();
432 mMediaPlayer.setDataSource(fd);
433 } else if (mUri.getScheme().compareTo("content") == 0) {
434 mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
435 } else {
436 mMediaPlayer.setDataSource(path);
437 }
438 setState(State.Initialized);
439 } catch (final Exception exception) {
440 Log.w(TAG, exception);
441 } finally {
442 try {
443 if (afd != null)
444 afd.close();
445 if (fis != null)
446 fis.close();
447 } catch (final IOException ioe) { /* Ignore... */ }
448
449 if ((mState & State.Initialized) == 0) {
450 setState(State.Error);
451 onErrorNative(MediaPlayer.MEDIA_ERROR_UNKNOWN,
452 -1004 /*MEDIA_ERROR_IO*/,
453 mID);
454 return;
455 }
456 }
457 }
458
459 private boolean isMediaPlayerPrepared()
460 {
461 int preparedState = (State.Prepared | State.Started | State.Paused | State.Stopped
462 | State.PlaybackCompleted);
463 return ((mState & preparedState) != 0);
464 }
465
466 TrackInfo[] getAllTrackInfo()
467 {
468 if (!isMediaPlayerPrepared()) {
469 Log.w(TAG, "Trying to get track info of a media player that is not prepared!");
470 return new TrackInfo[0];
471 }
472
473 MediaPlayer.TrackInfo[] tracks = new MediaPlayer.TrackInfo[0];
474
475 try {
476 // media player will ignore if this a out bounds index.
477 tracks = mMediaPlayer.getTrackInfo();
478 } catch (final IllegalStateException exception) {
479 Log.w(TAG, exception);
480 }
481
482 int numberOfTracks = tracks.length;
483 TrackInfo[] qtTracksInfo = new TrackInfo[numberOfTracks];
484
485 for (int index = 0; index < numberOfTracks; index++) {
486
487 MediaPlayer.TrackInfo track = tracks[index];
488
489 int type = track.getTrackType();
490 String mimeType = getMimeType(track);
491 String language = track.getLanguage();
492
493 qtTracksInfo[index] = new TrackInfo(type, mimeType, language);
494 }
495
496 return qtTracksInfo;
497 }
498
499 private String getMimeType(MediaPlayer.TrackInfo trackInfo)
500 {
501 // The "octet-stream" subtype is used to indicate that a body contains arbitrary binary
502 // data.
503 String defaultMimeType = "application/octet-stream";
504
505 String mimeType = defaultMimeType;
506
507 MediaFormat mediaFormat = trackInfo.getFormat();
508 if (mediaFormat != null) {
509 mimeType = mediaFormat.getString(MediaFormat.KEY_MIME, defaultMimeType);
510 }
511
512 return mimeType;
513 }
514
515 void selectTrack(int index)
516 {
517 if (!isMediaPlayerPrepared()) {
518 Log.d(TAG, "Trying to select a track of a media player that is not prepared!");
519 return;
520 }
521 try {
522 // media player will ignore if this a out bounds index.
523 mMediaPlayer.selectTrack(index);
524 } catch (final IllegalStateException exception) {
525 Log.w(TAG, exception);
526 }
527 }
528
529 void deselectTrack(int index)
530 {
531 if (!isMediaPlayerPrepared()) {
532 Log.d(TAG, "Trying to deselect track of a media player that is not prepared!");
533 return;
534 }
535
536 try {
537 // media player will ignore if this a out bounds index.
538 mMediaPlayer.deselectTrack(index);
539 } catch (final IllegalStateException exception) {
540 Log.w(TAG, exception);
541 }
542 }
543
544 int getSelectedTrack(int type)
545 {
546
547 int InvalidTrack = -1;
548 if (!isMediaPlayerPrepared()) {
549 Log.d(TAG, "Trying to get the selected track of a media player that is not prepared!");
550 return InvalidTrack;
551 }
552
553 boolean isVideoTrackType = (type == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO);
554 boolean isAudioTrackType = (type == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO);
555 boolean isTimedTextTrackType = (type == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT);
556 boolean isSubtitleTrackType = (type == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
557
558 if (!(isVideoTrackType || isAudioTrackType || isSubtitleTrackType
559 || isTimedTextTrackType)) {
560 Log.w(TAG,
561 "Trying to get a selected track of a invalid type"
562 + " Only Video,Audio, TimedText and Subtitle tracks are selectable.");
563 return InvalidTrack;
564 }
565
566 try {
567 return mMediaPlayer.getSelectedTrack(type);
568 } catch (final IllegalStateException exception) {
569 Log.w(TAG, exception);
570 }
571
572 return InvalidTrack;
573 }
574
575 int getCurrentPosition()
576 {
577 int currentPosition = 0;
578 if ((mState & (State.Idle
579 | State.Initialized
580 | State.Prepared
581 | State.Started
582 | State.Paused
583 | State.Stopped
584 | State.PlaybackCompleted)) == 0) {
585 return currentPosition;
586 }
587
588 try {
589 currentPosition = mMediaPlayer.getCurrentPosition();
590 } catch (final IllegalStateException exception) {
591 Log.w(TAG, exception);
592 }
593
594 return currentPosition;
595 }
596
597
598 int getDuration()
599 {
600 int duration = 0;
601 if ((mState & (State.Prepared
602 | State.Started
603 | State.Paused
604 | State.Stopped
605 | State.PlaybackCompleted)) == 0) {
606 return duration;
607 }
608
609 try {
610 duration = mMediaPlayer.getDuration();
611 } catch (final IllegalStateException exception) {
612 Log.w(TAG, exception);
613 }
614
615 return duration;
616 }
617
618 void setVolume(int volume)
619 {
620 if (volume < 0)
621 volume = 0;
622
623 if (volume > 100)
624 volume = 100;
625
626 mVolume = volume;
627
628 if (!mMuted)
629 setVolumeHelper(mVolume);
630 }
631
632 private void setVolumeHelper(int volume)
633 {
634 if ((mState & (State.Idle
635 | State.Initialized
636 | State.Stopped
637 | State.Prepared
638 | State.Started
639 | State.Paused
640 | State.PlaybackCompleted)) == 0) {
641 return;
642 }
643
644 try {
645 float newVolume = (float)volume / 100;
646 mMediaPlayer.setVolume(newVolume, newVolume);
647 } catch (final IllegalStateException exception) {
648 Log.w(TAG, exception);
649 }
650 }
651
652 SurfaceHolder display()
653 {
654 return mSurfaceHolder;
655 }
656
657 void setDisplay(SurfaceHolder sh)
658 {
659 mSurfaceHolder = sh;
660
661 if ((mState & State.Uninitialized) != 0)
662 return;
663
664 mMediaPlayer.setDisplay(mSurfaceHolder);
665 }
666
667
668 int getVolume()
669 {
670 return mVolume;
671 }
672
673 void mute(final boolean mute)
674 {
675 mMuted = mute;
676 setVolumeHelper(mute ? 0 : mVolume);
677 }
678
679 boolean isMuted()
680 {
681 return mMuted;
682 }
683
684 void reset()
685 {
686 if (mState == State.Uninitialized) {
687 return;
688 }
689
690 mMediaPlayer.reset();
691 setState(State.Idle);
692 cancelProgressWatcher();
693 }
694
695 void release()
696 {
697 if (mMediaPlayer != null) {
698 mMediaPlayer.reset();
699 mMediaPlayer.release();
700 mMediaPlayer = null;
701 }
702
703 setState(State.Uninitialized);
704 cancelProgressWatcher();
705 }
706
707 void setAudioAttributes(int type, int usage)
708 {
709 mAudioAttributes = new AudioAttributes.Builder()
710 .setUsage(usage)
711 .setContentType(type)
712 .build();
713
714 setAudioAttributes(mMediaPlayer, mAudioAttributes);
715 }
716
717 static private void setAudioAttributes(MediaPlayer player, AudioAttributes attr)
718 {
719 if (player == null || attr == null)
720 return;
721
722 try {
723 player.setAudioAttributes(attr);
724 } catch (final IllegalArgumentException exception) {
725 Log.w(TAG, exception);
726 }
727 }
728
729 boolean setPlaybackRate(float rate)
730 {
731 PlaybackParams playbackParams = mMediaPlayer.getPlaybackParams();
732 playbackParams.setSpeed(rate);
733 // According to discussion under the patch from QTBUG-61115: At least with DirectShow
734 // and GStreamer, it changes both speed and pitch. (...) need to be consistent
735 if (rate != 0.0)
736 playbackParams.setPitch(Math.abs(rate));
737
738 try {
739 mMediaPlayer.setPlaybackParams(playbackParams);
740 } catch (IllegalStateException | IllegalArgumentException e) {
741 Log.e(TAG, "Cannot set playback rate " + rate + " :" + e.toString());
742 return false;
743 }
744
745 if ((mState & State.Started) == 0 && mMediaPlayer.isPlaying()) {
746 setState(State.Started);
747 startProgressWatcher();
748 }
749
750 return true;
751 }
752}
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)
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)
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)
QMediaPlayer player
Definition audio.cpp:213
PeripheralState state
\inmodule QtGui
QPainter Context
static const QString context()
Definition java.cpp:398
struct wl_display * display
Definition linuxdmabuf.h:41
static QString header(const QString &name)
EGLOutputLayerEXT EGLint EGLAttrib value
[3]
const char * mimeType
constexpr int Uninitialized
#define TAG(x)
GLenum GLuint id
GLuint start
GLenum type
GLenum GLuint GLenum GLsizei length
GLuint index
GLint GLsizei width
GLenum GLuint GLintptr offset
GLuint64 GLenum GLint fd
GLboolean reset
GLuint GLenum * rate
GLsizei const GLchar *const * path
EGLint EGLint usage
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
textPart setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\""))