Skip to content

Commit

Permalink
Merge pull request #7146 from vector-im/phlpro/voice_broadcast_backwa…
Browse files Browse the repository at this point in the history
…rd_forward

VoiceBroadcast: Add backward and forward buttons for playback
  • Loading branch information
Phl-Pro authored Dec 14, 2022
2 parents a484c28 + b579ad6 commit 34159cb
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "voice_broadcast_backward_30s.svg",
"idiom" : "universal"
}
],
"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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "voice_broadcast_forward_30s.svg",
"idiom" : "universal"
}
],
"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.
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,7 @@
"images" : [
{
"filename" : "voice_broadcast_spinner.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
"idiom" : "universal"
}
],
"info" : {
Expand Down
2 changes: 2 additions & 0 deletions Riot/Generated/Images.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ internal class Asset: NSObject {
internal static let tabHome = ImageAsset(name: "tab_home")
internal static let tabPeople = ImageAsset(name: "tab_people")
internal static let tabRooms = ImageAsset(name: "tab_rooms")
internal static let voiceBroadcastBackward30s = ImageAsset(name: "voice_broadcast_backward_30s")
internal static let voiceBroadcastForward30s = ImageAsset(name: "voice_broadcast_forward_30s")
internal static let voiceBroadcastLive = ImageAsset(name: "voice_broadcast_live")
internal static let voiceBroadcastPause = ImageAsset(name: "voice_broadcast_pause")
internal static let voiceBroadcastPlay = ImageAsset(name: "voice_broadcast_play")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
return (!isPlaybackInitialized || isPlayingLastChunk) && (state.broadcastState == .started || state.broadcastState == .resumed)
}

private static let defaultBackwardForwardValue: Float = 30000.0 // 30sec in ms

// MARK: Public

// MARK: - Setup
Expand All @@ -71,7 +73,7 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
let viewState = VoiceBroadcastPlaybackViewState(details: details,
broadcastState: voiceBroadcastAggregator.voiceBroadcastState,
playbackState: .stopped,
playingState: VoiceBroadcastPlayingState(duration: Float(voiceBroadcastAggregator.voiceBroadcast.duration), isLive: false),
playingState: VoiceBroadcastPlayingState(duration: Float(voiceBroadcastAggregator.voiceBroadcast.duration), isLive: false, canMoveForward: false, canMoveBackward: false),
bindings: VoiceBroadcastPlaybackViewStateBindings(progress: 0))
super.init(initialViewState: viewState)

Expand Down Expand Up @@ -101,6 +103,10 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
pause()
case .sliderChange(let didChange):
didSliderChanged(didChange)
case .backward:
backward()
case .forward:
forward()
}
}

Expand Down Expand Up @@ -164,6 +170,49 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
audioPlayer?.stop()
}

/// Backward (30sec) a voice broadcast
private func backward() {
let newProgressValue = context.progress - VoiceBroadcastPlaybackViewModel.defaultBackwardForwardValue
seek(to: max(newProgressValue, 0.0))
}

/// Forward (30sec) a voice broadcast
private func forward() {
let newProgressValue = context.progress + VoiceBroadcastPlaybackViewModel.defaultBackwardForwardValue
seek(to: min(newProgressValue, state.playingState.duration))
}

private func seek(to seekTime: Float) {
// Flush the chunks queue and the current audio player playlist
voiceBroadcastChunkQueue = []
reloadVoiceBroadcastChunkQueue = isProcessingVoiceBroadcastChunk
audioPlayer?.removeAllPlayerItems()

let chunks = reorderVoiceBroadcastChunks(chunks: Array(voiceBroadcastAggregator.voiceBroadcast.chunks))

// Reinject the chunks we need and play them
let remainingTime = state.playingState.duration - seekTime
var chunksDuration: UInt = 0
for chunk in chunks.reversed() {
chunksDuration += chunk.duration
voiceBroadcastChunkQueue.append(chunk)
if Float(chunksDuration) >= remainingTime {
break
}
}

MXLog.debug("[VoiceBroadcastPlaybackViewModel] seekTo: restart to time: \(seekTime) milliseconds")
let time = seekTime - 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()

state.bindings.progress = seekTime
updateUI()
}

// MARK: - Voice broadcast chunks playback

Expand Down Expand Up @@ -295,40 +344,11 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
audioPlayer?.pause()
displayLink.isPaused = true
} else {
// Flush the chunks queue and the current audio player playlist
voiceBroadcastChunkQueue = []
reloadVoiceBroadcastChunkQueue = isProcessingVoiceBroadcastChunk
audioPlayer?.removeAllPlayerItems()

let chunks = reorderVoiceBroadcastChunks(chunks: Array(voiceBroadcastAggregator.voiceBroadcast.chunks))

// Reinject the chunks we need and play them
let remainingTime = state.playingState.duration - state.bindings.progress
var chunksDuration: UInt = 0
for chunk in chunks.reversed() {
chunksDuration += chunk.duration
voiceBroadcastChunkQueue.append(chunk)
if Float(chunksDuration) >= remainingTime {
break
}
}

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()
seek(to: state.bindings.progress)
}
}

@objc private func handleDisplayLinkTick() {
updateUI()
}

private func updateUI() {
guard let playingEventId = voiceBroadcastAttachmentCacheManagerLoadResults.first(where: { result in
result.url == audioPlayer?.currentUrl
})?.eventIdentifier,
Expand All @@ -343,6 +363,13 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
}.reduce(0) { $0 + $1.duration}) + (audioPlayer?.currentTime.rounded() ?? 0) * 1000

state.bindings.progress = Float(progress)

updateUI()
}

private func updateUI() {
state.playingState.canMoveBackward = state.bindings.progress > 0
state.playingState.canMoveForward = state.bindings.progress < state.playingState.duration
}

private func handleVoiceBroadcastChunksProcessing() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,19 @@ struct VoiceBroadcastPlaybackView: View {
if viewModel.viewState.playbackState == .error {
VoiceBroadcastPlaybackErrorView()
} else {
ZStack {
HStack (spacing: 17.0) {
if viewModel.viewState.playingState.canMoveBackward {
Button {
viewModel.send(viewAction: .backward)
} label: {
Image(uiImage: Asset.Images.voiceBroadcastBackward30s.image)
.renderingMode(.original)
}
.accessibilityIdentifier("backwardButton")
} else {
Spacer().frame(width: 25.0)
}

if viewModel.viewState.playbackState == .playing || viewModel.viewState.playbackState == .buffering {
Button { viewModel.send(viewAction: .pause) } label: {
Image(uiImage: Asset.Images.voiceBroadcastPause.image)
Expand All @@ -125,6 +137,18 @@ struct VoiceBroadcastPlaybackView: View {
.disabled(viewModel.viewState.playbackState == .buffering)
.accessibilityIdentifier("playButton")
}

if viewModel.viewState.playingState.canMoveForward {
Button {
viewModel.send(viewAction: .forward)
} label: {
Image(uiImage: Asset.Images.voiceBroadcastForward30s.image)
.renderingMode(.original)
}
.accessibilityIdentifier("forwardButton")
} else {
Spacer().frame(width: 25.0)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ enum VoiceBroadcastPlaybackViewAction {
case play
case pause
case sliderChange(didChange: Bool)
case backward
case forward
}

enum VoiceBroadcastPlaybackState {
Expand All @@ -40,6 +42,8 @@ struct VoiceBroadcastPlayingState {
var duration: Float
var durationLabel: String?
var isLive: Bool
var canMoveForward: Bool
var canMoveBackward: Bool
}

struct VoiceBroadcastPlaybackViewState: BindableState {
Expand Down
Loading

0 comments on commit 34159cb

Please sign in to comment.