Merge pull request #7125 from vector-im/phlpro/voicebroadcast_pause_playback_buffering

VoiceBroadcast: Be able to pause the playback when it is buffering
This commit is contained in:
Phl-Pro
2022-12-01 08:36:38 +01:00
committed by GitHub
8 changed files with 96 additions and 41 deletions
@@ -137,6 +137,9 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
if let audioPlayer = audioPlayer, audioPlayer.isPlaying {
audioPlayer.pause()
} else {
state.playbackState = .paused
state.playingState.isLive = false
}
}
@@ -196,10 +199,6 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
return
}
if (isActuallyPaused == false && state.playbackState == .paused) {
state.playbackState = .buffering
}
guard !isProcessingVoiceBroadcastChunk else {
// Chunks caching is already in progress
return
@@ -233,41 +232,41 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
self.voiceBroadcastAttachmentCacheManagerLoadResults.append(result)
if let audioPlayer = self.audioPlayer {
// Append the chunk to the current playlist
audioPlayer.addContentFromURL(result.url)
if let time = self.seekToChunkTime {
audioPlayer.seekToTime(time)
self.seekToChunkTime = nil
}
// Resume the player. Needed after a buffering
if self.state.playbackState == .buffering {
if audioPlayer.isPlaying == false {
MXLog.debug("[VoiceBroadcastPlaybackViewModel] processNextVoiceBroadcastChunk: Resume the player")
self.displayLink.isPaused = false
audioPlayer.play()
} else {
self.state.playbackState = .playing
self.state.playingState.isLive = self.isLivePlayback
}
}
} else {
// Instanciate audioPlayer if needed.
if self.audioPlayer == nil {
// Init and start the player on the first chunk
let audioPlayer = self.mediaServiceProvider.audioPlayerForIdentifier(result.eventIdentifier)
audioPlayer.registerDelegate(self)
audioPlayer.loadContentFromURL(result.url, displayName: chunk.attachment.originalFileName)
self.displayLink.isPaused = false
audioPlayer.play()
if let time = self.seekToChunkTime {
audioPlayer.seekToTime(time)
self.seekToChunkTime = nil
}
self.audioPlayer = audioPlayer
} else {
// Append the chunk to the current playlist
self.audioPlayer?.addContentFromURL(result.url)
}
guard let audioPlayer = self.audioPlayer else {
MXLog.error("[VoiceBroadcastPlaybackViewModel] processVoiceBroadcastChunkQueue: audioPlayer is nil !")
return
}
// Start or Resume the player. Needed after a buffering
if self.state.playbackState == .buffering {
if audioPlayer.isPlaying == false {
MXLog.debug("[VoiceBroadcastPlaybackViewModel] processNextVoiceBroadcastChunk: Start or Resume the player")
self.displayLink.isPaused = false
audioPlayer.play()
} else {
self.state.playbackState = .playing
self.state.playingState.isLive = self.isLivePlayback
}
}
if let time = self.seekToChunkTime {
audioPlayer.seekToTime(time)
self.seekToChunkTime = nil
}
case .failure (let error):
MXLog.error("[VoiceBroadcastPlaybackViewModel] processVoiceBroadcastChunkQueue: loadAttachment error", context: error)
if self.voiceBroadcastChunkQueue.count == 0 {
@@ -317,6 +316,10 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
MXLog.debug("[VoiceBroadcastPlaybackViewModel] didSliderChanged: restart to time: \(state.bindings.progress) milliseconds")
let time = state.bindings.progress - state.playingState.duration + Float(chunksDuration)
seekToChunkTime = TimeInterval(time / 1000)
// Check the condition to resume the playback when data will be ready (after the chunk process).
if state.playbackState != .stopped, isActuallyPaused == false {
state.playbackState = .buffering
}
processPendingVoiceBroadcastChunks()
}
}
@@ -380,7 +383,7 @@ extension VoiceBroadcastPlaybackViewModel: VoiceBroadcastAggregatorDelegate {
updateDuration()
if state.playbackState != .stopped {
if state.playbackState != .stopped, !isActuallyPaused {
handleVoiceBroadcastChunksProcessing()
}
}
@@ -30,6 +30,7 @@ struct VoiceBroadcastPlaybackView: View {
// MARK: Private
@Environment(\.theme) private var theme: ThemeSwiftUI
@State private var bufferingSpinnerRotationValue = 0.0
private var backgroundColor: Color {
if viewModel.viewState.playingState.isLive {
@@ -61,12 +62,33 @@ struct VoiceBroadcastPlaybackView: View {
} icon: {
Image(uiImage: Asset.Images.voiceBroadcastTileMic.image)
}
Label {
Text(VectorL10n.voiceBroadcastTile)
.foregroundColor(theme.colors.secondaryContent)
.font(theme.fonts.caption1)
} icon: {
Image(uiImage: Asset.Images.voiceBroadcastTileLive.image)
if viewModel.viewState.playbackState != .buffering {
Label {
Text(VectorL10n.voiceBroadcastTile)
.foregroundColor(theme.colors.secondaryContent)
.font(theme.fonts.caption1)
} icon: {
Image(uiImage: Asset.Images.voiceBroadcastTileLive.image)
}
} else {
Label {
Text(VectorL10n.voiceBroadcastBuffering)
.foregroundColor(theme.colors.secondaryContent)
.font(theme.fonts.caption1)
} icon: {
Image(uiImage: Asset.Images.voiceBroadcastSpinner.image)
.frame(width: 16.0, height: 16.0)
.rotationEffect(Angle.degrees(bufferingSpinnerRotationValue))
.onAppear {
let baseAnimation = Animation.linear(duration: 1.0).repeatForever(autoreverses: false)
withAnimation(baseAnimation) {
bufferingSpinnerRotationValue = 360.0
}
}
.onDisappear {
bufferingSpinnerRotationValue = 0.0
}
}
}
}.frame(maxWidth: .infinity, alignment: .leading)
@@ -89,13 +111,13 @@ struct VoiceBroadcastPlaybackView: View {
VoiceBroadcastPlaybackErrorView()
} else {
ZStack {
if viewModel.viewState.playbackState == .playing {
if viewModel.viewState.playbackState == .playing || viewModel.viewState.playbackState == .buffering {
Button { viewModel.send(viewAction: .pause) } label: {
Image(uiImage: Asset.Images.voiceBroadcastPause.image)
.renderingMode(.original)
}
.accessibilityIdentifier("pauseButton")
} else {
} else {
Button { viewModel.send(viewAction: .play) } label: {
Image(uiImage: Asset.Images.voiceBroadcastPlay.image)
.renderingMode(.original)
@@ -104,7 +126,6 @@ struct VoiceBroadcastPlaybackView: View {
.accessibilityIdentifier("playButton")
}
}
.activityIndicator(show: viewModel.viewState.playbackState == .buffering)
}
Slider(value: $viewModel.progress, in: 0...viewModel.viewState.playingState.duration) {