Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Voice Broadcast] Use internal playback timer to compute the playback position #8012

Merged
merged 14 commits into from
Jan 31, 2023

Conversation

Florian14
Copy link
Contributor

@Florian14 Florian14 commented Jan 26, 2023

Type of change

  • Feature
  • Bugfix
  • Technical
  • Other :

Content

Rely on an internal playback timer to display the current playback position instead of relying on the position given by the android media player.

Note:
In my opinion, this is not an ideal solution, I reused and improved the existing CountUpTimer which is not based on real elapsed time. Under the hood, it's a tickerFlow which notifies at a fixed interval (50 ms in our case) except if we pause it. It can shift a bit for about +/- the intervalMs on each pause/resume depending on the timing.
Btw, I noticed that a too-low interval (eg. 10 ms) will break the timer. I also noticed perfs issues after enabling the debugger on android studio, the Flow takes too much time to notify. I don't know if a busy device can have the same kind of issue.

Motivation and context

Voice broadcasts sent by Element-Web were causing trouble in the playback position computation.
The chunks sent from the web are encoded in ogg format with aggregated metadata which is not the case for the other VB sent from iOS or Android.

Also, when the recorder is paused on the web, the aggregation is reset, so we cannot rely on the ogg file metadata to calculate the current playback position. The android media player also gives the aggregated position and duration of these files causing glitches in the playback rendering.

I did not find a way to get the relative position in the android MediaPlayer API. Moreover, there are some glitches when we switch to the next file, MediaPlayer.currentPosition starts returning the relative position (eg. 0ms/250ms/375ms) and then jumps to the aggregatedPosition (eg. 15_000ms...) but always returns the aggregated duration (eg. 45_000ms for a chunk of 15_000ms, supposing we are listening to the third chunk).

Screenshots / GIFs

Tests

  • Have several VB with several chunks (you can set the chunk duration to 15s on web in the /devtools options) recording from the web and iOS or Android
  • Verify that the playback position is correcty updated on each VB
  • Verify that the playback position is still correct after moving the cursor
  • Stress a bit the playback by switching to another one or playing with the controls (pause/resume)

Tested devices

  • Physical
  • Emulator
  • OS version(s):

Checklist


package im.vector.lib.core.utils.timer

interface Clock {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not find a better way instead of duplicating this file. I have issues with DI after moving the existing clock interface from the vector module into this one. Also, I cannot move the CountUpTimer to the vector module because another lib is using it.

@Florian14 Florian14 requested review from a team, jonnyandrew and bmarty and removed request for a team January 27, 2023 17:05
@Florian14 Florian14 marked this pull request as ready for review January 27, 2023 17:05
@@ -270,6 +282,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration)
}
playingState == State.Playing || playingState == State.Buffering -> {
stopPlayer()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it right that when the playing state is State.Playing that we call stopPlayer()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as the user requested to listen from another position (potentially in another chunk file), we stop the current player and will start a new one just two lines later. If we don't stop it, the playback position which is saved in the next line could be erased by the current player

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... but you're right, this is not necessary, it is already done in startPlayback method. I'll remove it from this place

Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one question, not blocking.

init {
startCounter()
}

private fun startCounter() {
tickerFlow(coroutineScope, intervalInMs / 10)
tickerFlow(coroutineScope, intervalInMs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/ 10 was used for this reason: c69bc12

Are you sure you want to revert this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bmarty The elapsed time and the current time are updated when the timer is paused & resumed (see https://github.com/vector-im/element-android/blob/941d0e5cf4c509fbc1ef6d3f29fa4242266dc74e/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt#L58-L66), it should be more precise now and the previous trick should not be necessary anymore. Moreover, the timer does not work anymore if the interval is too low (eg. under 10 ms), the ticks are too close and it does not notify in the expected delay (it can take more than 2 seconds to notify 1 second...), so it is better to keep the real interval value and do not divide the value by ten to prevent this bad behaviour.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the code again, I think user can observe an extra delay if we keep a tick per second.
The pb is that the tickerFlow ticks every second, and not when the UI has to be udpated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed together IRL, @Florian14 will make a dedicated PR to improve CountUpTimer implementation, and make it respect its expected contract. Ideally with some tests around it.

@Florian14
Copy link
Contributor Author

Florian14 commented Jan 30, 2023

@Florian14 Florian14 added the Z-NextRelease For issues and PRs which should be included in the NextRelease. label Jan 31, 2023
Copy link
Contributor

@yostyle yostyle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM. I did some tests.

@sonarcloud
Copy link

sonarcloud bot commented Jan 31, 2023

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

0.0% 0.0% Coverage
0.0% 0.0% Duplication

@Florian14 Florian14 merged commit ca37cc5 into develop Jan 31, 2023
@Florian14 Florian14 deleted the bugfix/fre/fix_vb_scrubbing branch January 31, 2023 10:19
@Florian14 Florian14 mentioned this pull request Feb 1, 2023
15 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Z-NextRelease For issues and PRs which should be included in the NextRelease.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants