Hi,
We are trying to develop a Karaoke app. We already developed a version on iOS with _AVPlayer_ and everything works great but on Android we have an issue we cannot solve for a long time.
App consists of animation layer (JS/WebView animation) and music. Animation is embedded in WebView and it has method _update(songCurrentTime)_ which has to be invoked periodically to synchronize animation with song.
We use _Handler_ and _runOnUiThread_ for update loop. We update animation with song position (_getCurrentPosition()_) every ~50ms.
Everything works great in 100% cases when we start playing music. Then we can pause/play again and it’s also ok. Then we want to use seek. When we want to seek to position that is already buffered - everything is ok. But when we use seekTo on position that it’s not already buffered - we encounter a problem. After buffering (_onPlayerStateChanged_) and playing again - animation is not synchronized with music. It’s usually about +/- 300ms desynchronized. Sometimes it’s ok, but usually it’s not. When it's desynchronized we can pause/play but it remains the same.
We don’t calculate anything after buffering, animation handle going back and forward, we update it on loop normally so it looks like _getCurrentPosition()_ returns wrong time.
We tried to update animation with calculated time after buffering (using _System.currentTimeMillis()_) but it cannot work because _playWhenReady_ etc. is not synchronized method - playing probably starts in other thread so we don’t have the exact time of player starting playing.
The strangest thing is that it only happens when buffering is required.
We thought it may be something with playing and then buffering and playing again. But scenario: play -> pause -> seekTo -> wait until buffered -> play - results the same.
It’s hard to show this desynchronization in logs, those under are for the pause/buffer scenario above (player pos is player _getCurrentPosition_) in which delay is about 200ms (animation is about 200ms before music).
[14:15:26.178] SONG player pos = 4555
[14:15:26.208] SONG PAUSE clicked player pos = 4574
[14:15:26.208] SONG onPlayerStateChanged = 3
[14:15:26.208] SONG IS BUFFERING = false
[14:15:26.208] SONG player pos = 4574
[14:15:31.323] SONG seek to 169000, player pos = 169000
[14:15:31.343] SONG onPlayerStateChanged = 2
[14:15:31.343] SONG IS BUFFERING = true
[14:15:31.343] SONG player pos = 169000
[14:15:35.618] SONG onPlayerStateChanged = 3
[14:15:35.618] SONG IS BUFFERING = false
[14:15:35.618] SONG player pos = 169066
[14:15:44.646] SONG PLAY clicked player pos = 169066
[14:15:44.646] SONG onPlayerStateChanged = 3
[14:15:44.646] SONG IS BUFFERING = false
[14:15:44.646] SONG player pos = 169066
[14:15:44.666] SONG player pos = 169066
[14:15:44.726] SONG player pos = 169116
[14:15:44.797] SONG player pos = 169182
[14:15:44.857] SONG player pos = 169242
This is how we implement basic ExoPlayer:
bandwidthMeter = new DefaultBandwidthMeter();

extractorsFactory = new DefaultExtractorsFactory();

trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);

trackSelector = new DefaultTrackSelector(trackSelectionFactory);

defaultBandwidthMeter = new DefaultBandwidthMeter();


dataSourceFactory = new DefaultDataSourceFactory(context,
 Util.getUserAgent(context, "mediaPlayerSample"), defaultBandwidthMeter);


mediaSource = new ExtractorMediaSource(Uri.parse(songUrl), dataSourceFactory, extractorsFactory, null, null);
exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector);

exoPlayer.prepare(mediaSource);

exoPlayer.setPlayWhenReady(true);
What’s very interesting is that we encountered the same problem on Android’s default _MediaPlayer_ - that’s why we used ExoPlayer. Maybe this is some low-level-api issues or we do something wrong. Animation is 100% ok because it works on iOS (with buffering etc) and also it works on Android without seek or with buffered seek.
We really don’t know what to do now. If anyone experienced something similar or have a clue for us - we will be very grateful.
We tested it on many Android devices including: Huawei P9, Samsung Galaxy J3, HTC Desire, Nexus 5 and few others.
Regards.
If you're playing mp3 files, that's your problem. You should use a container format that's better suited for exact seeking, since that's a core requirement for your use case. MP4, MKV and WebM would all be appropriate choices. There's some additional context regarding exact seeking in mp3 files here.
That was exactly that! Thx!
Great :). For added context: It is theoretically possible to seek accurately in mp3 files. In the worst case the player could just download the whole stream, parse it and build its own sample accurate time to byte-offset index.
It's more the case that it's not possible to seek accurately in an efficient way. Where this is the case we opt to do an approximate seek instead, because for most use cases (e.g. if you were just implementing a podcast player) that would be preferable to having the player do something inefficient, and for use cases where exact seeking is important, solving the problem by using a better suited container format is the right thing to do.
Most helpful comment
If you're playing mp3 files, that's your problem. You should use a container format that's better suited for exact seeking, since that's a core requirement for your use case. MP4, MKV and WebM would all be appropriate choices. There's some additional context regarding exact seeking in mp3 files here.