mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00

Fixed music playback panel glitches Added ability to display more than 100 message search results Fixed instagram videos playback
734 lines
29 KiB
Swift
734 lines
29 KiB
Swift
import Foundation
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
|
|
import TelegramUIPrivateModule
|
|
|
|
enum SharedMediaPlayerPlaybackControlAction {
|
|
case play
|
|
case pause
|
|
case togglePlayPause
|
|
}
|
|
|
|
enum SharedMediaPlayerControlAction {
|
|
case next
|
|
case previous
|
|
case playback(SharedMediaPlayerPlaybackControlAction)
|
|
case seek(Double)
|
|
case setOrder(MusicPlaybackSettingsOrder)
|
|
case setLooping(MusicPlaybackSettingsLooping)
|
|
case setBaseRate(AudioPlaybackRate)
|
|
}
|
|
|
|
enum SharedMediaPlaylistControlAction {
|
|
case next
|
|
case previous
|
|
}
|
|
|
|
enum SharedMediaPlaybackDataType {
|
|
case music
|
|
case voice
|
|
case instantVideo
|
|
}
|
|
|
|
enum SharedMediaPlaybackDataSource: Equatable {
|
|
case telegramFile(FileMediaReference)
|
|
|
|
static func ==(lhs: SharedMediaPlaybackDataSource, rhs: SharedMediaPlaybackDataSource) -> Bool {
|
|
switch lhs {
|
|
case let .telegramFile(lhsFileReference):
|
|
if case let .telegramFile(rhsFileReference) = rhs {
|
|
if !lhsFileReference.media.isEqual(to: rhsFileReference.media) {
|
|
return false
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SharedMediaPlaybackData: Equatable {
|
|
let type: SharedMediaPlaybackDataType
|
|
let source: SharedMediaPlaybackDataSource
|
|
|
|
static func ==(lhs: SharedMediaPlaybackData, rhs: SharedMediaPlaybackData) -> Bool {
|
|
return lhs.type == rhs.type && lhs.source == rhs.source
|
|
}
|
|
}
|
|
|
|
struct SharedMediaPlaybackAlbumArt: Equatable {
|
|
let thumbnailResource: TelegramMediaResource
|
|
let fullSizeResource: TelegramMediaResource
|
|
|
|
static func ==(lhs: SharedMediaPlaybackAlbumArt, rhs: SharedMediaPlaybackAlbumArt) -> Bool {
|
|
if !lhs.thumbnailResource.isEqual(to: rhs.thumbnailResource) {
|
|
return false
|
|
}
|
|
|
|
if !lhs.fullSizeResource.isEqual(to: rhs.fullSizeResource) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
enum SharedMediaPlaybackDisplayData: Equatable {
|
|
case music(title: String?, performer: String?, albumArt: SharedMediaPlaybackAlbumArt?)
|
|
case voice(author: Peer?, peer: Peer?)
|
|
case instantVideo(author: Peer?, peer: Peer?, timestamp: Int32)
|
|
|
|
static func ==(lhs: SharedMediaPlaybackDisplayData, rhs: SharedMediaPlaybackDisplayData) -> Bool {
|
|
switch lhs {
|
|
case let .music(lhsTitle, lhsPerformer, lhsAlbumArt):
|
|
if case let .music(rhsTitle, rhsPerformer, rhsAlbumArt) = rhs, lhsTitle == rhsTitle, lhsPerformer == rhsPerformer, lhsAlbumArt == rhsAlbumArt {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .voice(lhsAuthor, lhsPeer):
|
|
if case let .voice(rhsAuthor, rhsPeer) = rhs, arePeersEqual(lhsAuthor, rhsAuthor), arePeersEqual(lhsPeer, rhsPeer) {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .instantVideo(lhsAuthor, lhsPeer, lhsTimestamp):
|
|
if case let .instantVideo(rhsAuthor, rhsPeer, rhsTimestamp) = rhs, arePeersEqual(lhsAuthor, rhsAuthor), arePeersEqual(lhsPeer, rhsPeer), lhsTimestamp == rhsTimestamp {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protocol SharedMediaPlaylistItem {
|
|
var stableId: AnyHashable { get }
|
|
var id: SharedMediaPlaylistItemId { get }
|
|
var playbackData: SharedMediaPlaybackData? { get }
|
|
var displayData: SharedMediaPlaybackDisplayData? { get }
|
|
}
|
|
|
|
func arePlaylistItemsEqual(_ lhs: SharedMediaPlaylistItem?, _ rhs: SharedMediaPlaylistItem?) -> Bool {
|
|
if lhs?.stableId != rhs?.stableId {
|
|
return false
|
|
}
|
|
if lhs?.playbackData != rhs?.playbackData {
|
|
return false
|
|
}
|
|
if lhs?.displayData != rhs?.displayData {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
final class SharedMediaPlaylistState: Equatable {
|
|
let loading: Bool
|
|
let playedToEnd: Bool
|
|
let item: SharedMediaPlaylistItem?
|
|
let nextItem: SharedMediaPlaylistItem?
|
|
let previousItem: SharedMediaPlaylistItem?
|
|
let order: MusicPlaybackSettingsOrder
|
|
let looping: MusicPlaybackSettingsLooping
|
|
|
|
init(loading: Bool, playedToEnd: Bool, item: SharedMediaPlaylistItem?, nextItem: SharedMediaPlaylistItem?, previousItem: SharedMediaPlaylistItem?, order: MusicPlaybackSettingsOrder, looping: MusicPlaybackSettingsLooping) {
|
|
self.loading = loading
|
|
self.playedToEnd = playedToEnd
|
|
self.item = item
|
|
self.nextItem = nextItem
|
|
self.previousItem = previousItem
|
|
self.order = order
|
|
self.looping = looping
|
|
}
|
|
|
|
static func ==(lhs: SharedMediaPlaylistState, rhs: SharedMediaPlaylistState) -> Bool {
|
|
if lhs.loading != rhs.loading {
|
|
return false
|
|
}
|
|
if !arePlaylistItemsEqual(lhs.item, rhs.item) {
|
|
return false
|
|
}
|
|
if !arePlaylistItemsEqual(lhs.nextItem, rhs.nextItem) {
|
|
return false
|
|
}
|
|
if !arePlaylistItemsEqual(lhs.previousItem, rhs.previousItem) {
|
|
return false
|
|
}
|
|
if lhs.order != rhs.order {
|
|
return false
|
|
}
|
|
if lhs.looping != rhs.looping {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
protocol SharedMediaPlaylistId {
|
|
func isEqual(to: SharedMediaPlaylistId) -> Bool
|
|
}
|
|
|
|
protocol SharedMediaPlaylistItemId {
|
|
func isEqual(to: SharedMediaPlaylistItemId) -> Bool
|
|
}
|
|
|
|
func areSharedMediaPlaylistItemIdsEqual(_ lhs: SharedMediaPlaylistItemId?, _ rhs: SharedMediaPlaylistItemId?) -> Bool {
|
|
if let lhs = lhs, let rhs = rhs {
|
|
return lhs.isEqual(to: rhs)
|
|
} else if (lhs != nil) != (rhs != nil) {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
protocol SharedMediaPlaylistLocation {
|
|
func isEqual(to: SharedMediaPlaylistLocation) -> Bool
|
|
}
|
|
|
|
protocol SharedMediaPlaylist {
|
|
var id: SharedMediaPlaylistId { get }
|
|
var location: SharedMediaPlaylistLocation { get }
|
|
var state: Signal<SharedMediaPlaylistState, NoError> { get }
|
|
var looping: MusicPlaybackSettingsLooping { get }
|
|
|
|
func control(_ action: SharedMediaPlaylistControlAction)
|
|
func setOrder(_ order: MusicPlaybackSettingsOrder)
|
|
func setLooping(_ looping: MusicPlaybackSettingsLooping)
|
|
|
|
func onItemPlaybackStarted(_ item: SharedMediaPlaylistItem)
|
|
}
|
|
|
|
final class SharedMediaPlayerItemPlaybackState: Equatable {
|
|
let playlistId: SharedMediaPlaylistId
|
|
let playlistLocation: SharedMediaPlaylistLocation
|
|
let item: SharedMediaPlaylistItem
|
|
let status: MediaPlayerStatus
|
|
let order: MusicPlaybackSettingsOrder
|
|
let looping: MusicPlaybackSettingsLooping
|
|
let playerIndex: Int32
|
|
|
|
init(playlistId: SharedMediaPlaylistId, playlistLocation: SharedMediaPlaylistLocation, item: SharedMediaPlaylistItem, status: MediaPlayerStatus, order: MusicPlaybackSettingsOrder, looping: MusicPlaybackSettingsLooping, playerIndex: Int32) {
|
|
self.playlistId = playlistId
|
|
self.playlistLocation = playlistLocation
|
|
self.item = item
|
|
self.status = status
|
|
self.order = order
|
|
self.looping = looping
|
|
self.playerIndex = playerIndex
|
|
}
|
|
|
|
static func ==(lhs: SharedMediaPlayerItemPlaybackState, rhs: SharedMediaPlayerItemPlaybackState) -> Bool {
|
|
if !lhs.playlistId.isEqual(to: rhs.playlistId) {
|
|
return false
|
|
}
|
|
if !arePlaylistItemsEqual(lhs.item, rhs.item) {
|
|
return false
|
|
}
|
|
if lhs.status != rhs.status {
|
|
return false
|
|
}
|
|
if lhs.playerIndex != rhs.playerIndex {
|
|
return false
|
|
}
|
|
if lhs.order != rhs.order {
|
|
return false
|
|
}
|
|
if lhs.looping != rhs.looping {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
enum SharedMediaPlayerState: Equatable {
|
|
case loading
|
|
case item(SharedMediaPlayerItemPlaybackState)
|
|
|
|
static func ==(lhs: SharedMediaPlayerState, rhs: SharedMediaPlayerState) -> Bool {
|
|
switch lhs {
|
|
case .loading:
|
|
if case .loading = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .item(item):
|
|
if case .item(item) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum SharedMediaPlaybackItem: Equatable {
|
|
case audio(MediaPlayer)
|
|
case instantVideo(OverlayInstantVideoNode)
|
|
|
|
var playbackStatus: Signal<MediaPlayerStatus, NoError> {
|
|
switch self {
|
|
case let .audio(player):
|
|
return player.status
|
|
case let .instantVideo(node):
|
|
return node.status |> map { status in
|
|
if let status = status {
|
|
return status
|
|
} else {
|
|
return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static func ==(lhs: SharedMediaPlaybackItem, rhs: SharedMediaPlaybackItem) -> Bool {
|
|
switch lhs {
|
|
case let .audio(lhsPlayer):
|
|
if case let .audio(rhsPlayer) = rhs, lhsPlayer === rhsPlayer {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .instantVideo(lhsNode):
|
|
if case let .instantVideo(rhsNode) = rhs, lhsNode === rhsNode {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
func setActionAtEnd(_ f: @escaping () -> Void) {
|
|
switch self {
|
|
case let .audio(player):
|
|
player.actionAtEnd = .action(f)
|
|
case let .instantVideo(node):
|
|
node.playbackEnded = f
|
|
}
|
|
}
|
|
|
|
func play() {
|
|
switch self {
|
|
case let .audio(player):
|
|
player.play()
|
|
case let .instantVideo(node):
|
|
node.play()
|
|
}
|
|
}
|
|
|
|
func pause() {
|
|
switch self {
|
|
case let .audio(player):
|
|
player.pause()
|
|
case let .instantVideo(node):
|
|
node.pause()
|
|
}
|
|
}
|
|
|
|
func togglePlayPause() {
|
|
switch self {
|
|
case let .audio(player):
|
|
player.togglePlayPause()
|
|
case let .instantVideo(node):
|
|
node.togglePlayPause()
|
|
}
|
|
}
|
|
|
|
func seek(_ timestamp: Double) {
|
|
switch self {
|
|
case let .audio(player):
|
|
player.seek(timestamp: timestamp)
|
|
case let .instantVideo(node):
|
|
node.seek(timestamp)
|
|
}
|
|
}
|
|
|
|
func setSoundEnabled(_ value: Bool) {
|
|
switch self {
|
|
case .audio:
|
|
break
|
|
case let .instantVideo(node):
|
|
node.setSoundEnabled(value)
|
|
}
|
|
}
|
|
|
|
func setForceAudioToSpeaker(_ value: Bool) {
|
|
switch self {
|
|
case let .audio(player):
|
|
player.setForceAudioToSpeaker(value)
|
|
case let .instantVideo(node):
|
|
node.setForceAudioToSpeaker(value)
|
|
}
|
|
}
|
|
}
|
|
|
|
final class SharedMediaPlayer {
|
|
private weak var mediaManager: MediaManager?
|
|
private let postbox: Postbox
|
|
private let audioSession: ManagedAudioSession
|
|
private let overlayMediaManager: OverlayMediaManager
|
|
private let playerIndex: Int32
|
|
private let playlist: SharedMediaPlaylist
|
|
|
|
private var playbackRate: AudioPlaybackRate
|
|
|
|
private var proximityManagerIndex: Int?
|
|
private let controlPlaybackWithProximity: Bool
|
|
private var forceAudioToSpeaker = false
|
|
|
|
private var stateDisposable: Disposable?
|
|
|
|
private var stateValue: SharedMediaPlaylistState? {
|
|
didSet {
|
|
if self.stateValue != oldValue {
|
|
self.state.set(.single(self.stateValue))
|
|
}
|
|
}
|
|
}
|
|
private let state = Promise<SharedMediaPlaylistState?>(nil)
|
|
|
|
private var playbackStateValueDisposable: Disposable?
|
|
private var _playbackStateValue: SharedMediaPlayerState?
|
|
private let playbackStateValue = Promise<SharedMediaPlayerState?>()
|
|
var playbackState: Signal<SharedMediaPlayerState?, NoError> {
|
|
return self.playbackStateValue.get()
|
|
}
|
|
|
|
private var playbackItem: SharedMediaPlaybackItem?
|
|
private var currentPlayedToEnd = false
|
|
private var scheduledPlaybackAction: SharedMediaPlayerPlaybackControlAction?
|
|
|
|
private let markItemAsPlayedDisposable = MetaDisposable()
|
|
|
|
var playedToEnd: (() -> Void)?
|
|
|
|
private var inForegroundDisposable: Disposable?
|
|
|
|
private var currentPrefetchItems: (SharedMediaPlaybackDataSource, SharedMediaPlaybackDataSource)?
|
|
private let prefetchDisposable = MetaDisposable()
|
|
|
|
init(mediaManager: MediaManager, inForeground: Signal<Bool, NoError>, postbox: Postbox, audioSession: ManagedAudioSession, overlayMediaManager: OverlayMediaManager, playlist: SharedMediaPlaylist, initialOrder: MusicPlaybackSettingsOrder, initialLooping: MusicPlaybackSettingsLooping, initialPlaybackRate: AudioPlaybackRate, playerIndex: Int32, controlPlaybackWithProximity: Bool) {
|
|
self.mediaManager = mediaManager
|
|
self.postbox = postbox
|
|
self.audioSession = audioSession
|
|
self.overlayMediaManager = overlayMediaManager
|
|
playlist.setOrder(initialOrder)
|
|
playlist.setLooping(initialLooping)
|
|
self.playlist = playlist
|
|
self.playerIndex = playerIndex
|
|
self.playbackRate = initialPlaybackRate
|
|
self.controlPlaybackWithProximity = controlPlaybackWithProximity
|
|
|
|
if controlPlaybackWithProximity {
|
|
self.forceAudioToSpeaker = !DeviceProximityManager.shared().currentValue()
|
|
}
|
|
|
|
self.stateDisposable = (playlist.state
|
|
|> deliverOnMainQueue).start(next: { [weak self] state in
|
|
if let strongSelf = self {
|
|
let previousPlaybackItem = strongSelf.playbackItem
|
|
strongSelf.updatePrefetchItems(item: state.item, previousItem: state.previousItem, nextItem: state.nextItem, ordering: state.order)
|
|
if state.item?.playbackData != strongSelf.stateValue?.item?.playbackData {
|
|
if let playbackItem = strongSelf.playbackItem {
|
|
switch playbackItem {
|
|
case .audio:
|
|
playbackItem.pause()
|
|
case let .instantVideo(node):
|
|
node.setSoundEnabled(false)
|
|
strongSelf.overlayMediaManager.controller?.removeNode(node)
|
|
}
|
|
}
|
|
strongSelf.playbackItem = nil
|
|
if let item = state.item, let playbackData = item.playbackData {
|
|
let rateValue: Double
|
|
if case .music = playbackData.type {
|
|
rateValue = 1.0
|
|
} else {
|
|
switch strongSelf.playbackRate {
|
|
case .x1:
|
|
rateValue = 1.0
|
|
case .x2:
|
|
rateValue = 1.8
|
|
}
|
|
}
|
|
|
|
switch playbackData.type {
|
|
case .voice, .music:
|
|
switch playbackData.source {
|
|
case let .telegramFile(fileReference):
|
|
strongSelf.playbackItem = .audio(MediaPlayer(audioSessionManager: strongSelf.audioSession, postbox: strongSelf.postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: playbackData.type == .music, video: false, preferSoftwareDecoding: false, enableSound: true, baseRate: rateValue, fetchAutomatically: true, playAndRecord: controlPlaybackWithProximity))
|
|
}
|
|
case .instantVideo:
|
|
if let mediaManager = strongSelf.mediaManager, let item = item as? MessageMediaPlaylistItem {
|
|
switch playbackData.source {
|
|
case let .telegramFile(fileReference):
|
|
let videoNode = OverlayInstantVideoNode(postbox: strongSelf.postbox, audioSession: strongSelf.audioSession, manager: mediaManager.universalVideoManager, content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, fileReference.media.fileId), fileReference: fileReference, streamVideo: false, enableSound: false, baseRate: rateValue), close: { [weak mediaManager] in
|
|
mediaManager?.setPlaylist(nil, type: .voice)
|
|
})
|
|
strongSelf.playbackItem = .instantVideo(videoNode)
|
|
videoNode.setSoundEnabled(true)
|
|
videoNode.setBaseRate(rateValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let playbackItem = strongSelf.playbackItem {
|
|
playbackItem.setForceAudioToSpeaker(strongSelf.forceAudioToSpeaker)
|
|
playbackItem.setActionAtEnd({
|
|
Queue.mainQueue().async {
|
|
if let strongSelf = self {
|
|
switch strongSelf.playlist.looping {
|
|
case .item:
|
|
strongSelf.playbackItem?.seek(0.0)
|
|
strongSelf.playbackItem?.play()
|
|
default:
|
|
strongSelf.scheduledPlaybackAction = .play
|
|
strongSelf.control(.next)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
switch playbackItem {
|
|
case .audio:
|
|
break
|
|
case let .instantVideo(node):
|
|
strongSelf.overlayMediaManager.controller?.addNode(node)
|
|
}
|
|
|
|
if let scheduledPlaybackAction = strongSelf.scheduledPlaybackAction {
|
|
strongSelf.scheduledPlaybackAction = nil
|
|
switch scheduledPlaybackAction {
|
|
case .play:
|
|
switch playbackItem {
|
|
case let .audio(player):
|
|
player.play()
|
|
case let .instantVideo(node):
|
|
node.playOnceWithSound(playAndRecord: controlPlaybackWithProximity)
|
|
}
|
|
case .pause:
|
|
playbackItem.pause()
|
|
case .togglePlayPause:
|
|
playbackItem.togglePlayPause()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if strongSelf.currentPlayedToEnd != state.playedToEnd {
|
|
strongSelf.currentPlayedToEnd = state.playedToEnd
|
|
if state.playedToEnd {
|
|
if let playbackItem = strongSelf.playbackItem {
|
|
switch playbackItem {
|
|
case let .audio(player):
|
|
player.pause()
|
|
case let .instantVideo(node):
|
|
node.setSoundEnabled(false)
|
|
}
|
|
}
|
|
//strongSelf.playbackItem?.seek(0.0)
|
|
strongSelf.playedToEnd?()
|
|
}
|
|
}
|
|
|
|
let updatePlaybackState = strongSelf.stateValue != state || strongSelf.playbackItem != previousPlaybackItem
|
|
strongSelf.stateValue = state
|
|
|
|
if updatePlaybackState {
|
|
let playlistId = strongSelf.playlist.id
|
|
let playlistLocation = strongSelf.playlist.location
|
|
let playerIndex = strongSelf.playerIndex
|
|
if let playbackItem = strongSelf.playbackItem, let item = state.item {
|
|
strongSelf.playbackStateValue.set(playbackItem.playbackStatus
|
|
|> map { itemStatus in
|
|
return .item(SharedMediaPlayerItemPlaybackState(playlistId: playlistId, playlistLocation: playlistLocation, item: item, status: itemStatus, order: state.order, looping: state.looping, playerIndex: playerIndex))
|
|
})
|
|
strongSelf.markItemAsPlayedDisposable.set((playbackItem.playbackStatus
|
|
|> filter { status in
|
|
if case .playing = status.status {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { next in
|
|
if let strongSelf = self {
|
|
strongSelf.playlist.onItemPlaybackStarted(item)
|
|
}
|
|
}))
|
|
} else {
|
|
if state.item != nil || state.loading {
|
|
strongSelf.playbackStateValue.set(.single(.loading))
|
|
} else {
|
|
strongSelf.playbackStateValue.set(.single(nil))
|
|
if !state.loading {
|
|
if let proximityManagerIndex = strongSelf.proximityManagerIndex {
|
|
DeviceProximityManager.shared().remove(proximityManagerIndex)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
self.playbackStateValueDisposable = (self.playbackState
|
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|
self?._playbackStateValue = value
|
|
})
|
|
|
|
if controlPlaybackWithProximity {
|
|
self.proximityManagerIndex = DeviceProximityManager.shared().add { [weak self] value in
|
|
let forceAudioToSpeaker = !value
|
|
if let strongSelf = self, strongSelf.forceAudioToSpeaker != forceAudioToSpeaker {
|
|
strongSelf.forceAudioToSpeaker = forceAudioToSpeaker
|
|
strongSelf.playbackItem?.setForceAudioToSpeaker(forceAudioToSpeaker)
|
|
if !forceAudioToSpeaker {
|
|
strongSelf.control(.playback(.play))
|
|
} else {
|
|
strongSelf.control(.playback(.pause))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.stateDisposable?.dispose()
|
|
self.markItemAsPlayedDisposable.dispose()
|
|
self.inForegroundDisposable?.dispose()
|
|
self.playbackStateValueDisposable?.dispose()
|
|
self.prefetchDisposable.dispose()
|
|
|
|
if let proximityManagerIndex = self.proximityManagerIndex {
|
|
DeviceProximityManager.shared().remove(proximityManagerIndex)
|
|
}
|
|
|
|
if let playbackItem = self.playbackItem {
|
|
switch playbackItem {
|
|
case .audio:
|
|
playbackItem.pause()
|
|
case let .instantVideo(node):
|
|
node.setSoundEnabled(false)
|
|
self.overlayMediaManager.controller?.removeNode(node)
|
|
}
|
|
}
|
|
}
|
|
|
|
func control(_ action: SharedMediaPlayerControlAction) {
|
|
switch action {
|
|
case .next:
|
|
self.scheduledPlaybackAction = .play
|
|
self.playlist.control(.next)
|
|
case .previous:
|
|
let threshold: Double = 5.0
|
|
if let playbackStateValue = self._playbackStateValue, case let .item(item) = playbackStateValue, item.status.duration > threshold, item.status.timestamp > threshold {
|
|
self.control(.seek(0.0))
|
|
} else {
|
|
self.scheduledPlaybackAction = .play
|
|
self.playlist.control(.previous)
|
|
}
|
|
case let .playback(action):
|
|
if let playbackItem = self.playbackItem {
|
|
switch action {
|
|
case .play:
|
|
playbackItem.play()
|
|
case .pause:
|
|
playbackItem.pause()
|
|
case .togglePlayPause:
|
|
playbackItem.togglePlayPause()
|
|
}
|
|
} else {
|
|
self.scheduledPlaybackAction = action
|
|
}
|
|
case let .seek(timestamp):
|
|
if let playbackItem = self.playbackItem {
|
|
playbackItem.seek(timestamp)
|
|
}
|
|
case let .setOrder(order):
|
|
self.playlist.setOrder(order)
|
|
case let .setLooping(looping):
|
|
self.playlist.setLooping(looping)
|
|
case let .setBaseRate(baseRate):
|
|
self.playbackRate = baseRate
|
|
if let playbackItem = self.playbackItem {
|
|
let rateValue: Double
|
|
switch baseRate {
|
|
case .x1:
|
|
rateValue = 1.0
|
|
case .x2:
|
|
rateValue = 1.8
|
|
}
|
|
switch playbackItem {
|
|
case let .audio(player):
|
|
player.setBaseRate(rateValue)
|
|
|
|
case let .instantVideo(node):
|
|
node.setBaseRate(rateValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func stop() {
|
|
if let playbackItem = self.playbackItem {
|
|
switch playbackItem {
|
|
case let .audio(player):
|
|
player.pause()
|
|
case let .instantVideo(node):
|
|
node.setSoundEnabled(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func updatePrefetchItems(item: SharedMediaPlaylistItem?, previousItem: SharedMediaPlaylistItem?, nextItem: SharedMediaPlaylistItem?, ordering: MusicPlaybackSettingsOrder) {
|
|
var prefetchItems: (SharedMediaPlaybackDataSource, SharedMediaPlaybackDataSource)?
|
|
if let playbackData = item?.playbackData {
|
|
switch ordering {
|
|
case .regular:
|
|
if let previousItem = previousItem?.playbackData {
|
|
prefetchItems = (playbackData.source, previousItem.source)
|
|
}
|
|
case .reversed:
|
|
if let nextItem = nextItem?.playbackData {
|
|
prefetchItems = (playbackData.source, nextItem.source)
|
|
}
|
|
case .random:
|
|
break
|
|
}
|
|
}
|
|
if self.currentPrefetchItems?.0 != prefetchItems?.0 || self.currentPrefetchItems?.1 != prefetchItems?.1 {
|
|
self.currentPrefetchItems = prefetchItems
|
|
if let (current, next) = prefetchItems {
|
|
let fetchedCurrentSignal: Signal<Never, NoError>
|
|
let fetchedNextSignal: Signal<Never, NoError>
|
|
switch current {
|
|
case let .telegramFile(file):
|
|
fetchedCurrentSignal = self.postbox.mediaBox.resourceData(file.media.resource)
|
|
|> mapToSignal { data -> Signal<Void, NoError> in
|
|
if data.complete {
|
|
return .single(Void())
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
|> take(1)
|
|
|> ignoreValues
|
|
}
|
|
switch next {
|
|
case let .telegramFile(file):
|
|
fetchedNextSignal = fetchedMediaResource(postbox: self.postbox, reference: file.resourceReference(file.media.resource))
|
|
|> ignoreValues
|
|
}
|
|
self.prefetchDisposable.set((fetchedCurrentSignal |> then(fetchedNextSignal)).start())
|
|
} else {
|
|
self.prefetchDisposable.set(nil)
|
|
}
|
|
}
|
|
}
|
|
}
|