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 {
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);
170 onTrackInfoChangedNative(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 {
202 onVideoSizeChangedNative(width, height, mID);
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}
QMediaPlayer player
Definition audio.cpp:323
PeripheralState state
QPainter Context
static const QString context()
Definition java.cpp:396
struct wl_display * display
Definition linuxdmabuf.h:46
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
Language language()
Definition language.cpp:18
static QString header(const QString &name)
EGLOutputLayerEXT EGLint EGLAttrib value
[3]
const char * mimeType
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