Update the SDK. (#3565)

* Update the SDK.

* Add mockMXC URLs to fix all the crashes during tests.

* Refactor Voice Messages to not use a MediaSource for local files.
This commit is contained in:
Doug
2024-11-28 08:34:38 +00:00
committed by GitHub
parent e70a95cfa2
commit 751be7b3a7
75 changed files with 222 additions and 590 deletions
@@ -22,7 +22,7 @@ private enum InternalAudioPlayerState {
}
class AudioPlayer: NSObject, AudioPlayerProtocol {
var mediaSource: MediaSourceProxy?
var sourceURL: URL?
private var playerItem: AVPlayerItem?
private var internalAudioPlayer: AVQueuePlayer?
@@ -46,7 +46,7 @@ class AudioPlayer: NSObject, AudioPlayerProtocol {
private let releaseAudioSessionTimeoutInterval = 5.0
private(set) var url: URL?
private(set) var playbackURL: URL?
private var deinitInProgress = false
@@ -86,13 +86,13 @@ class AudioPlayer: NSObject, AudioPlayerProtocol {
unloadContent()
}
func load(mediaSource: MediaSourceProxy, using url: URL, autoplay: Bool) {
func load(sourceURL: URL, playbackURL: URL, autoplay: Bool) {
unloadContent()
setInternalState(.loading)
self.mediaSource = mediaSource
self.url = url
self.sourceURL = sourceURL
self.playbackURL = playbackURL
self.autoplay = autoplay
playerItem = AVPlayerItem(url: url)
playerItem = AVPlayerItem(url: playbackURL)
internalAudioPlayer = AVQueuePlayer(playerItem: playerItem)
addObservers()
}
@@ -162,8 +162,8 @@ class AudioPlayer: NSObject, AudioPlayerProtocol {
}
private func unloadContent() {
mediaSource = nil
url = nil
sourceURL = nil
playbackURL = nil
internalAudioPlayer?.replaceCurrentItem(with: nil)
internalAudioPlayer = nil
playerItem = nil
@@ -12,6 +12,17 @@ enum AudioPlayerError: Error {
case genericError
}
// There used to be a MediaPlayerProtocol that AudioPlayerProtocol inherited from.
// This should be called something else but we already have an AudioPlayerState,
// AudioPlayerPlaybackState and InternalAudioPlayerState so who knows what to call this.
enum MediaPlayerState {
case loading
case playing
case paused
case stopped
case error
}
enum AudioPlayerAction {
case didStartLoading
case didFinishLoading
@@ -22,8 +33,21 @@ enum AudioPlayerAction {
case didFailWithError(error: Error)
}
protocol AudioPlayerProtocol: MediaPlayerProtocol {
protocol AudioPlayerProtocol: AnyObject {
var sourceURL: URL? { get }
var duration: TimeInterval { get }
var currentTime: TimeInterval { get }
var playbackURL: URL? { get }
var state: MediaPlayerState { get }
var actions: AnyPublisher<AudioPlayerAction, Never> { get }
func load(sourceURL: URL, playbackURL: URL, autoplay: Bool)
func reset()
func play()
func pause()
func stop()
func seek(to progress: Double) async
}
// sourcery: AutoMockable
@@ -134,7 +134,7 @@ class AudioPlayerState: ObservableObject, Identifiable {
MXLog.info("updating duration: \(duration) -> \(audioPlayerDuration)")
duration = audioPlayerDuration
}
fileURL = audioPlayer?.url
fileURL = audioPlayer?.playbackURL
playbackState = .readyToPlay
case .didStartPlaying:
if let audioPlayer {
@@ -68,7 +68,7 @@ struct RoomPreviewDetails {
let topic: String?
let avatarURL: URL?
let memberCount: UInt
let isHistoryWorldReadable: Bool
let isHistoryWorldReadable: Bool?
let isJoined: Bool
let isInvited: Bool
let isPublic: Bool
@@ -24,7 +24,10 @@ struct MediaSourceProxy: Hashable {
}
init(url: URL, mimeType: String?) {
underlyingSource = mediaSourceFromUrl(url: url.absoluteString)
guard let mediaSource = try? MediaSource.fromUrl(url: url.absoluteString) else {
fatalError("Unable to create MediaSource from URL: \(url.absoluteString)")
}
underlyingSource = mediaSource
self.url = URL(string: underlyingSource.url())
self.mimeType = mimeType
}
@@ -1,34 +0,0 @@
//
// Copyright 2023, 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Foundation
enum MediaPlayerState {
case loading
case playing
case paused
case stopped
case error
}
protocol MediaPlayerProtocol: AnyObject {
var mediaSource: MediaSourceProxy? { get }
var duration: TimeInterval { get }
var currentTime: TimeInterval { get }
var url: URL? { get }
var state: MediaPlayerState { get }
func load(mediaSource: MediaSourceProxy, using url: URL, autoplay: Bool)
func reset()
func play()
func pause()
func stop()
func seek(to progress: Double) async
}
// sourcery: AutoMockable
extension MediaPlayerProtocol { }
@@ -11,24 +11,12 @@ class MediaPlayerProvider: MediaPlayerProviderProtocol {
private lazy var audioPlayer = AudioPlayer()
private var audioPlayerStates: [String: AudioPlayerState] = [:]
var player: AudioPlayerProtocol { audioPlayer }
deinit {
audioPlayerStates = [:]
}
func player(for mediaSource: MediaSourceProxy) -> Result<MediaPlayerProtocol, MediaPlayerProviderError> {
guard let mimeType = mediaSource.mimeType else {
MXLog.error("Unknown mime type")
return .failure(.unsupportedMediaType)
}
if mimeType.starts(with: "audio/") {
return .success(audioPlayer)
} else {
MXLog.error("Unsupported media type: \(mediaSource.mimeType ?? "unknown")")
return .failure(.unsupportedMediaType)
}
}
// MARK: - AudioPlayer
func playerState(for id: AudioPlayerStateIdentifier) -> AudioPlayerState? {
@@ -13,7 +13,7 @@ enum MediaPlayerProviderError: Error {
@MainActor
protocol MediaPlayerProviderProtocol {
func player(for mediaSource: MediaSourceProxy) -> Result<MediaPlayerProtocol, MediaPlayerProviderError>
var player: AudioPlayerProtocol { get }
func playerState(for id: AudioPlayerStateIdentifier) -> AudioPlayerState?
func register(audioPlayerState: AudioPlayerState)
@@ -243,7 +243,7 @@ struct VideoInfoProxy: Hashable {
}
static var mockVideo: VideoInfoProxy {
.init(source: .init(url: .picturesDirectory, mimeType: nil),
.init(source: .init(url: .mockMXCVideo, mimeType: nil),
duration: 100,
size: .init(width: 1920, height: 1080),
aspectRatio: 1.78,
@@ -292,14 +292,14 @@ struct ImageInfoProxy: Hashable {
}
static var mockImage: ImageInfoProxy {
.init(source: .init(url: .picturesDirectory, mimeType: "image/png"),
.init(source: .init(url: .mockMXCImage, mimeType: "image/png"),
size: .init(width: 100, height: 100),
aspectRatio: 1,
mimeType: "image/png")
}
static var mockThumbnail: ImageInfoProxy {
.init(source: .init(url: .picturesDirectory, mimeType: nil),
.init(source: .init(url: .mockMXCImage, mimeType: nil),
size: nil,
aspectRatio: nil,
mimeType: nil)
@@ -101,13 +101,12 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
await previewAudioPlayerState.attachAudioPlayer(audioPlayer)
}
if audioPlayer.url == url {
if audioPlayer.playbackURL == url {
audioPlayer.play()
return .success(())
}
let pendingMediaSource = MediaSourceProxy(url: url, mimeType: mp4accMimeType)
audioPlayer.load(mediaSource: pendingMediaSource, using: url, autoplay: true)
audioPlayer.load(sourceURL: url, playbackURL: url, autoplay: true)
return .success(())
}
@@ -242,10 +241,7 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
previewAudioPlayerState = await AudioPlayerState(id: .recorderPreview, title: L10n.commonVoiceMessage, duration: recordingDuration, waveform: EstimatedWaveform(data: []))
// Build the preview audio player
let mediaSource = MediaSourceProxy(url: url, mimeType: mp4accMimeType)
guard case .success(let mediaPlayer) = await mediaPlayerProvider.player(for: mediaSource), let audioPlayer = mediaPlayer as? AudioPlayerProtocol else {
return .failure(.previewNotAvailable)
}
let audioPlayer = await mediaPlayerProvider.player
previewAudioPlayer = audioPlayer
return .success(())