25
25
26
26
import android .content .Context ;
27
27
import android .net .Uri ;
28
+ import android .os .Handler ;
29
+ import android .os .Looper ;
28
30
import android .util .Log ;
29
31
import android .view .Surface ;
30
32
31
33
import androidx .annotation .NonNull ;
32
-
33
34
import androidx .media3 .common .C ;
34
- import androidx .media3 .exoplayer .DefaultLoadControl ;
35
- import androidx .media3 .exoplayer .ExoPlayer ;
35
+ import androidx .media3 .common .MediaItem ;
36
36
import androidx .media3 .common .PlaybackException ;
37
37
import androidx .media3 .common .Player ;
38
- import androidx .media3 .extractor .DefaultExtractorsFactory ;
39
- import androidx .media3 .extractor .ExtractorsFactory ;
40
- import androidx .media3 .exoplayer .source .MediaSource ;
41
- import androidx .media3 .exoplayer .source .ProgressiveMediaSource ;
38
+ import androidx .media3 .common .util .Util ;
39
+ import androidx .media3 .datasource .DataSource ;
40
+ import androidx .media3 .datasource .DefaultDataSourceFactory ;
41
+ import androidx .media3 .datasource .RawResourceDataSource ;
42
+ import androidx .media3 .exoplayer .DefaultLoadControl ;
43
+ import androidx .media3 .exoplayer .ExoPlayer ;
42
44
import androidx .media3 .exoplayer .dash .DashMediaSource ;
43
45
import androidx .media3 .exoplayer .hls .HlsMediaSource ;
44
46
import androidx .media3 .exoplayer .smoothstreaming .SsMediaSource ;
45
- import androidx .media3 .exoplayer .trackselection .AdaptiveTrackSelection ;
47
+ import androidx .media3 .exoplayer .source .MediaSource ;
48
+ import androidx .media3 .exoplayer .source .ProgressiveMediaSource ;
46
49
import androidx .media3 .exoplayer .trackselection .DefaultTrackSelector ;
47
- import androidx .media3 .datasource .DataSource ;
48
- import androidx .media3 .exoplayer .upstream .DefaultBandwidthMeter ;
49
- import androidx .media3 .datasource .DefaultDataSourceFactory ;
50
- import androidx .media3 .datasource .RawResourceDataSource ;
51
- import androidx .media3 .common .util .Util ;
52
- import androidx .media3 .common .MediaItem ;
50
+ import androidx .media3 .extractor .DefaultExtractorsFactory ;
51
+ import androidx .media3 .extractor .ExtractorsFactory ;
52
+
53
53
import com .google .common .base .Ascii ;
54
54
55
+ import java .util .concurrent .Callable ;
56
+ import java .util .concurrent .ExecutionException ;
57
+ import java .util .concurrent .FutureTask ;
58
+
55
59
/**
56
60
* Wraps the Android ExoPlayer and can be controlled via JNI.
57
61
*/
@@ -72,6 +76,9 @@ private enum State {
72
76
}
73
77
74
78
private final ExoPlayer mExoPlayer ;
79
+
80
+ private Handler mainThreadHandler = new Handler (Looper .getMainLooper ());
81
+
75
82
private float mVolume ;
76
83
private final long mNativeReference ;
77
84
private boolean mLoop ;
@@ -96,6 +103,7 @@ public AVPlayer(long nativeReference, Context context) {
96
103
97
104
@ Override
98
105
public void onPlayerStateChanged (boolean playWhenReady , int playbackState ) {
106
+ Log .i (TAG , "AVPlayer onPlayerStateChanged " + mPrevExoPlayerState + " => " + playbackState );
99
107
// this function sometimes gets called back w/ the same playbackState.
100
108
if (mPrevExoPlayerState == playbackState ) {
101
109
return ;
@@ -131,6 +139,24 @@ public void onPlayerError(@NonNull PlaybackException error) {
131
139
});
132
140
}
133
141
142
+ @ FunctionalInterface
143
+ public interface PlayerAction <T > {
144
+ T performAction (ExoPlayer player );
145
+ }
146
+
147
+ private <T > T runSynchronouslyOnMainThread (PlayerAction <T > action ) throws ExecutionException , InterruptedException {
148
+ Callable <T > callable = () -> action .performAction (mExoPlayer );
149
+ FutureTask <T > future = new FutureTask <>(callable );
150
+
151
+ mainThreadHandler .post (future );
152
+ try {
153
+ return future .get ();
154
+ } catch (Exception e ) {
155
+ Log .e (TAG , "AVPlayer ExoPlayer failed to run action on the main thread" , e );
156
+ throw e ;
157
+ }
158
+ }
159
+
134
160
public boolean setDataSourceURL (String resourceOrURL , final Context context ) {
135
161
try {
136
162
reset ();
@@ -157,14 +183,15 @@ public DataSource createDataSource() {
157
183
158
184
MediaSource mediaSource = buildMediaSource (uri , dataSourceFactory , extractorsFactory );
159
185
160
- mExoPlayer .prepare (mediaSource );
161
- mExoPlayer .seekToDefaultPosition ();
162
- mState = State .PREPARED ;
163
-
164
- Log .i (TAG , "AVPlayer prepared for playback" );
165
- nativeOnPrepared (mNativeReference );
166
-
167
- return true ;
186
+ return runSynchronouslyOnMainThread (player -> {
187
+ player .setMediaSource (mediaSource );
188
+ player .prepare ();
189
+ player .seekToDefaultPosition ();
190
+ mState = State .PREPARED ;
191
+ Log .i (TAG , "AVPlayer prepared for playback" );
192
+ nativeOnPrepared (mNativeReference );
193
+ return true ;
194
+ });
168
195
} catch (Exception e ) {
169
196
Log .w (TAG , "AVPlayer failed to load video at URL [" + resourceOrURL + "]" , e );
170
197
reset ();
@@ -208,15 +235,28 @@ private int inferContentType(String fileName) {
208
235
}
209
236
210
237
public void setVideoSink (Surface videoSink ) {
211
- mExoPlayer .setVideoSurface (videoSink );
238
+ try {
239
+ runSynchronouslyOnMainThread (player -> {
240
+ player .setVideoSurface (videoSink );
241
+ return null ;
242
+ });
243
+ } catch (Exception e ) {
244
+ Log .e (TAG , "AVPlayer failed to set video" , e );
245
+ }
212
246
}
213
247
214
248
public void reset () {
215
- mExoPlayer .stop ();
216
- mExoPlayer .seekToDefaultPosition ();
217
- mState = State .IDLE ;
218
-
219
- Log .i (TAG , "AVPlayer reset" );
249
+ try {
250
+ runSynchronouslyOnMainThread (player -> {
251
+ player .stop ();
252
+ player .seekToDefaultPosition ();
253
+ mState = State .IDLE ;
254
+ return null ;
255
+ });
256
+ Log .i (TAG , "AVPlayer reset" );
257
+ } catch (Exception e ) {
258
+ Log .e (TAG , "AVPlayer failed reset" , e );
259
+ }
220
260
}
221
261
222
262
public void destroy () {
@@ -228,17 +268,31 @@ public void destroy() {
228
268
229
269
public void play () {
230
270
if (mState == State .PREPARED || mState == State .PAUSED ) {
231
- mExoPlayer .setPlayWhenReady (true );
232
- mState = State .STARTED ;
271
+ try {
272
+ runSynchronouslyOnMainThread (player -> {
273
+ player .setPlayWhenReady (true );
274
+ mState = State .STARTED ;
275
+ return null ;
276
+ });
277
+ } catch (Exception e ) {
278
+ Log .e (TAG , "AVPlayer failed to play video" , e );
279
+ }
233
280
} else {
234
281
Log .w (TAG , "AVPlayer could not play video in " + mState .toString () + " state" );
235
282
}
236
283
}
237
284
238
285
public void pause () {
239
286
if (mState == State .STARTED ) {
240
- mExoPlayer .setPlayWhenReady (false );
241
- mState = State .PAUSED ;
287
+ try {
288
+ runSynchronouslyOnMainThread (player -> {
289
+ player .setPlayWhenReady (false );
290
+ mState = State .PAUSED ;
291
+ return null ;
292
+ });
293
+ } catch (Exception e ) {
294
+ Log .e (TAG , "AVPlayer failed to pause video" , e );
295
+ }
242
296
} else {
243
297
Log .w (TAG , "AVPlayer could not pause video in " + mState .toString () + " state" );
244
298
}
@@ -250,24 +304,46 @@ public boolean isPaused() {
250
304
251
305
public void setLoop (boolean loop ) {
252
306
mLoop = loop ;
253
- if (mExoPlayer .getPlaybackState () == ExoPlayer .STATE_ENDED ) {
254
- mExoPlayer .seekToDefaultPosition ();
307
+ try {
308
+ runSynchronouslyOnMainThread (player -> {
309
+ if (player .getPlaybackState () == ExoPlayer .STATE_ENDED ) {
310
+ player .seekToDefaultPosition ();
311
+ }
312
+ return null ;
313
+ });
314
+ } catch (Exception e ) {
315
+ Log .e (TAG , "AVPlayer failed to set loop" , e );
255
316
}
256
317
}
257
318
258
319
public void setVolume (float volume ) {
259
320
mVolume = volume ;
260
- if (!mMute ) {
261
- mExoPlayer .setVolume (mVolume );
321
+ if (mMute ) {
322
+ return ;
323
+ }
324
+ try {
325
+ runSynchronouslyOnMainThread (player -> {
326
+ player .setVolume (mVolume );
327
+ return null ;
328
+ });
329
+ } catch (Exception e ) {
330
+ Log .e (TAG , "AVPlayer failed to set volume" , e );
262
331
}
263
332
}
264
333
265
334
public void setMuted (boolean muted ) {
266
335
mMute = muted ;
267
- if (muted ) {
268
- mExoPlayer .setVolume (0 );
269
- } else {
270
- mExoPlayer .setVolume (mVolume );
336
+ try {
337
+ runSynchronouslyOnMainThread (player -> {
338
+ if (muted ) {
339
+ player .setVolume (0 );
340
+ } else {
341
+ player .setVolume (mVolume );
342
+ }
343
+ return null ;
344
+ });
345
+ } catch (Exception e ) {
346
+ Log .e (TAG , "AVPlayer failed to set muted " + muted , e );
271
347
}
272
348
}
273
349
@@ -276,8 +352,14 @@ public void seekToTime(float seconds) {
276
352
Log .w (TAG , "AVPlayer could not seek while in IDLE state" );
277
353
return ;
278
354
}
279
-
280
- mExoPlayer .seekTo ((long ) (seconds * 1000 ));
355
+ try {
356
+ runSynchronouslyOnMainThread (player -> {
357
+ player .seekTo ((long ) (seconds * 1000 ));
358
+ return null ;
359
+ });
360
+ } catch (Exception e ) {
361
+ Log .e (TAG , "AVPlayer failed to seek" , e );
362
+ }
281
363
}
282
364
283
365
public float getCurrentTimeInSeconds () {
@@ -286,18 +368,33 @@ public float getCurrentTimeInSeconds() {
286
368
return 0 ;
287
369
}
288
370
289
- return mExoPlayer .getCurrentPosition () / 1000.0f ;
371
+ long currentPosition = 0 ;
372
+ try {
373
+ currentPosition = runSynchronouslyOnMainThread (player -> player .getCurrentPosition ());
374
+ } catch (Exception e ) {
375
+ Log .e (TAG , "AVPlayer could not get video current position" , e );
376
+ }
377
+
378
+ return currentPosition / 1000.0f ;
290
379
}
291
380
292
381
public float getVideoDurationInSeconds () {
293
382
if (mState == State .IDLE ) {
294
383
Log .w (TAG , "AVPlayer could not get video duration in IDLE state" );
295
384
return 0 ;
296
- } else if (mExoPlayer .getDuration () == C .TIME_UNSET ) {
385
+ }
386
+
387
+ long duration = 0 ;
388
+ try {
389
+ duration = runSynchronouslyOnMainThread (player -> player .getDuration ());
390
+ } catch (Exception e ) {
391
+ Log .e (TAG , "AVPlayer could not get video duration" , e );
392
+ }
393
+ if (duration == C .TIME_UNSET ) {
297
394
return 0 ;
298
395
}
299
396
300
- return mExoPlayer . getDuration () / 1000.0f ;
397
+ return duration / 1000.0f ;
301
398
}
302
399
303
400
/**
0 commit comments