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

VoiceBroadcast: Be able to pause the playback when it is buffering #7125

Merged
merged 7 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "voice_broadcast_spinner.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Riot/Assets/en.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2203,6 +2203,7 @@ Tap the + to start adding people.";
"voice_broadcast_live" = "Live";
"voice_broadcast_tile" = "Voice broadcast";
"voice_broadcast_time_left" = "%@ left";
"voice_broadcast_buffering" = "Buffering...";

// Mark: - Version check

Expand Down
1 change: 1 addition & 0 deletions Riot/Generated/Images.swift
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ internal class Asset: NSObject {
internal static let voiceBroadcastPlay = ImageAsset(name: "voice_broadcast_play")
internal static let voiceBroadcastRecord = ImageAsset(name: "voice_broadcast_record")
internal static let voiceBroadcastRecordPause = ImageAsset(name: "voice_broadcast_record_pause")
internal static let voiceBroadcastSpinner = ImageAsset(name: "voice_broadcast_spinner")
internal static let voiceBroadcastStop = ImageAsset(name: "voice_broadcast_stop")
internal static let voiceBroadcastTileLive = ImageAsset(name: "voice_broadcast_tile_live")
internal static let voiceBroadcastTileMic = ImageAsset(name: "voice_broadcast_tile_mic")
Expand Down
4 changes: 4 additions & 0 deletions Riot/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9139,6 +9139,10 @@ public class VectorL10n: NSObject {
public static var voiceBroadcastBlockedBySomeoneElseMessage: String {
return VectorL10n.tr("Vector", "voice_broadcast_blocked_by_someone_else_message")
}
/// Buffering...
public static var voiceBroadcastBuffering: String {
return VectorL10n.tr("Vector", "voice_broadcast_buffering")
}
/// Live
public static var voiceBroadcastLive: String {
return VectorL10n.tr("Vector", "voice_broadcast_live")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic

if let audioPlayer = audioPlayer, audioPlayer.isPlaying {
audioPlayer.pause()
} else {
state.playbackState = .paused
state.playingState.isLive = false
}
giomfo marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
giomfo marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down Expand Up @@ -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()
}
}
Expand Down Expand Up @@ -380,7 +383,7 @@ extension VoiceBroadcastPlaybackViewModel: VoiceBroadcastAggregatorDelegate {

updateDuration()

if state.playbackState != .stopped {
if state.playbackState != .stopped, !isActuallyPaused {
handleVoiceBroadcastChunksProcessing()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions changelog.d/pr-7125.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Labs: VoiceBroadcast: Be able to pause the playback when it is buffering