mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-12 06:19:22 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
a4ea5e5642
@ -157,7 +157,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
|
||||
if true || (file.mimeType == "video/mpeg4" || file.mimeType == "video/mov" || file.mimeType == "video/mp4") {
|
||||
content = NativeVideoContent(id: .message(message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath)
|
||||
} else {
|
||||
content = PlatformVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), streamVideo: streamVideos, loopVideo: loopVideos)
|
||||
content = PlatformVideoContent(id: .message(message.id, message.stableId, file.fileId), content: .file(.message(message: MessageReference(message), media: file)), streamVideo: streamVideos, loopVideo: loopVideos)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ import AppBundle
|
||||
|
||||
public enum UniversalVideoGalleryItemContentInfo {
|
||||
case message(Message)
|
||||
case webPage(TelegramMediaWebpage, Media)
|
||||
case webPage(TelegramMediaWebpage, Media, ((@escaping () -> GalleryTransitionArguments?, NavigationController?, (ViewController, Any?) -> Void) -> Void)?)
|
||||
}
|
||||
|
||||
public class UniversalVideoGalleryItem: GalleryItem {
|
||||
@ -108,7 +108,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if case let .webPage(webPage, media) = contentInfo, let file = media as? TelegramMediaFile {
|
||||
} else if case let .webPage(webPage, media, _) = contentInfo, let file = media as? TelegramMediaFile {
|
||||
if let item = ChatMediaGalleryThumbnailItem(account: self.context.account, mediaReference: .webPage(webPage: WebpageReference(webPage), media: file)) {
|
||||
return (0, item)
|
||||
}
|
||||
@ -470,6 +470,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
var disablePictureInPicture = false
|
||||
var disablePlayerControls = false
|
||||
var forceEnablePiP = false
|
||||
var isAnimated = false
|
||||
if let content = item.content as? NativeVideoContent {
|
||||
isAnimated = content.fileReference.media.isAnimated
|
||||
@ -487,6 +488,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if let _ = item.content as? PlatformVideoContent {
|
||||
disablePlayerControls = true
|
||||
forceEnablePiP = true
|
||||
}
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
@ -511,7 +515,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
strongSelf.playOnContentOwnership = false
|
||||
strongSelf.initiallyActivated = true
|
||||
strongSelf.skipInitialPause = true
|
||||
strongSelf.videoNode?.playOnceWithSound(playAndRecord: false, actionAtEnd: isAnimated ? .loop : .stop)
|
||||
if let item = strongSelf.item, let _ = item.content as? PlatformVideoContent {
|
||||
strongSelf.videoNode?.play()
|
||||
} else {
|
||||
strongSelf.videoNode?.playOnceWithSound(playAndRecord: false, actionAtEnd: isAnimated ? .loop : .stop)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -698,7 +706,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
|
||||
barButtonItems.append(rightBarButtonItem)
|
||||
}
|
||||
if !isAnimated && !disablePlayerControls && !disablePictureInPicture {
|
||||
if forceEnablePiP || (!isAnimated && !disablePlayerControls && !disablePictureInPicture) {
|
||||
let rightBarButtonItem = UIBarButtonItem(image: pictureInPictureButtonImage, style: .plain, target: self, action: #selector(self.pictureInPictureButtonPressed))
|
||||
barButtonItems.append(rightBarButtonItem)
|
||||
self.hasPictureInPicture = true
|
||||
@ -725,9 +733,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
switch contentInfo {
|
||||
case let .message(message):
|
||||
self.footerContentNode.setMessage(message)
|
||||
case let .webPage(webPage, media):
|
||||
case let .webPage(webPage, media, _):
|
||||
self.footerContentNode.setWebPage(webPage, media: media)
|
||||
break
|
||||
}
|
||||
}
|
||||
self.footerContentNode.setup(origin: item.originData, caption: item.caption)
|
||||
@ -757,7 +764,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
private func shouldAutoplayOnCentrality() -> Bool {
|
||||
// !self.initiallyActivated
|
||||
if let item = self.item, let content = item.content as? NativeVideoContent {
|
||||
var isLocal = false
|
||||
if let fetchStatus = self.fetchStatus, case .Local = fetchStatus {
|
||||
@ -772,6 +778,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if isLocal || isStreamable {
|
||||
return true
|
||||
}
|
||||
} else if let item = self.item, let _ = item.content as? PlatformVideoContent {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -1335,8 +1343,27 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
case .webPage:
|
||||
break
|
||||
case let .webPage(_, _, expandFromPip):
|
||||
if let expandFromPip = expandFromPip, let baseNavigationController = baseNavigationController {
|
||||
expandFromPip({ [weak overlayNode] in
|
||||
if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode {
|
||||
return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in
|
||||
return (overlayNode?.view.snapshotContentTree(), nil)
|
||||
}), addToTransitionSurface: { [weak context, weak overlaySupernode, weak overlayNode] view in
|
||||
guard let context = context, let overlayNode = overlayNode else {
|
||||
return
|
||||
}
|
||||
if context.sharedContext.mediaManager.hasOverlayVideoNode(overlayNode) {
|
||||
overlaySupernode?.view.addSubview(view)
|
||||
}
|
||||
overlayNode.canAttachContent = false
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}, baseNavigationController, { [weak baseNavigationController] c, a in
|
||||
(baseNavigationController?.topViewController as? ViewController)?.present(c, in: .window(.root), with: a)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if customUnembedWhenPortrait(overlayNode) {
|
||||
@ -1398,8 +1425,27 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
case .webPage:
|
||||
break
|
||||
case let .webPage(_, _, expandFromPip):
|
||||
if let expandFromPip = expandFromPip, let baseNavigationController = baseNavigationController {
|
||||
expandFromPip({ [weak overlayNode] in
|
||||
if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode {
|
||||
return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in
|
||||
return (overlayNode?.view.snapshotContentTree(), nil)
|
||||
}), addToTransitionSurface: { [weak context, weak overlaySupernode, weak overlayNode] view in
|
||||
guard let context = context, let overlayNode = overlayNode else {
|
||||
return
|
||||
}
|
||||
if context.sharedContext.mediaManager.hasOverlayVideoNode(overlayNode) {
|
||||
overlaySupernode?.view.addSubview(view)
|
||||
}
|
||||
overlayNode.canAttachContent = false
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}, baseNavigationController, { [weak baseNavigationController] c, a in
|
||||
(baseNavigationController?.topViewController as? ViewController)?.present(c, in: .window(.root), with: a)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
context.sharedContext.mediaManager.setOverlayVideoNode(overlayNode)
|
||||
|
@ -113,7 +113,7 @@ public struct InstantPageGalleryEntry: Equatable {
|
||||
nativeId = .instantPage(self.pageId, file.fileId)
|
||||
}
|
||||
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: nativeId, fileReference: .webPage(webPage: WebpageReference(webPage), media: file), streamVideo: isMediaStreamable(media: file) ? .conservative : .none), originData: nil, indexData: indexData, contentInfo: .webPage(webPage, file), caption: caption, credit: credit, fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in }, present: { _, _ in })
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: nativeId, fileReference: .webPage(webPage: WebpageReference(webPage), media: file), streamVideo: isMediaStreamable(media: file) ? .conservative : .none), originData: nil, indexData: indexData, contentInfo: .webPage(webPage, file, nil), caption: caption, credit: credit, fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in }, present: { _, _ in })
|
||||
} else {
|
||||
var representations: [TelegramMediaImageRepresentation] = []
|
||||
representations.append(contentsOf: file.previewRepresentations)
|
||||
@ -124,10 +124,24 @@ public struct InstantPageGalleryEntry: Equatable {
|
||||
return InstantImageGalleryItem(context: context, presentationData: presentationData, itemId: self.index, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: caption, credit: credit, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions)
|
||||
}
|
||||
} else if let embedWebpage = self.media.media as? TelegramMediaWebpage, case let .Loaded(webpageContent) = embedWebpage.content {
|
||||
if let content = WebEmbedVideoContent(webPage: embedWebpage, webpageContent: webpageContent) {
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in }, present: { _, _ in })
|
||||
if webpageContent.url.hasSuffix(".m3u8") {
|
||||
let content = PlatformVideoContent(id: .instantPage(embedWebpage.webpageId, embedWebpage.webpageId), content: .url(webpageContent.url), streamVideo: true, loopVideo: false)
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage, { makeArguments, navigationController, present in
|
||||
let gallery = InstantPageGalleryController(context: context, webPage: webPage, entries: [self], centralIndex: 0, replaceRootController: { [weak navigationController] controller, ready in
|
||||
if let navigationController = navigationController {
|
||||
navigationController.replaceTopController(controller, animated: false, ready: ready)
|
||||
}
|
||||
}, baseNavigationController: navigationController)
|
||||
present(gallery, InstantPageGalleryControllerPresentationArguments(transitionArguments: { entry -> GalleryTransitionArguments? in
|
||||
return makeArguments()
|
||||
}))
|
||||
}), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in }, present: { _, _ in })
|
||||
} else {
|
||||
preconditionFailure()
|
||||
if let content = WebEmbedVideoContent(webPage: embedWebpage, webpageContent: webpageContent) {
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage, nil), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in }, present: { _, _ in })
|
||||
} else {
|
||||
preconditionFailure()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
preconditionFailure()
|
||||
@ -169,6 +183,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
|
||||
private let centralItemTitle = Promise<String>()
|
||||
private let centralItemTitleView = Promise<UIView?>()
|
||||
private let centralItemRightBarButtonItem = Promise<UIBarButtonItem?>()
|
||||
private let centralItemRightBarButtonItems = Promise<[UIBarButtonItem]?>(nil)
|
||||
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
|
||||
private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>()
|
||||
private let centralItemAttributesDisposable = DisposableSet();
|
||||
@ -239,8 +254,15 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
|
||||
self?.navigationItem.titleView = titleView
|
||||
}))
|
||||
|
||||
self.centralItemAttributesDisposable.add(self.centralItemRightBarButtonItem.get().start(next: { [weak self] rightBarButtonItem in
|
||||
self?.navigationItem.rightBarButtonItem = rightBarButtonItem
|
||||
self.centralItemAttributesDisposable.add(combineLatest(self.centralItemRightBarButtonItem.get(), self.centralItemRightBarButtonItems.get()).start(next: { [weak self] rightBarButtonItem, rightBarButtonItems in
|
||||
if let rightBarButtonItem = rightBarButtonItem {
|
||||
self?.navigationItem.rightBarButtonItem = rightBarButtonItem
|
||||
} else if let rightBarButtonItems = rightBarButtonItems {
|
||||
self?.navigationItem.rightBarButtonItems = rightBarButtonItems
|
||||
} else {
|
||||
self?.navigationItem.rightBarButtonItem = nil
|
||||
self?.navigationItem.rightBarButtonItems = nil
|
||||
}
|
||||
}))
|
||||
|
||||
self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode, _ in
|
||||
@ -362,6 +384,11 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
|
||||
self.galleryNode.completeCustomDismiss = { [weak self] in
|
||||
self?._hiddenMedia.set(.single(nil))
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
|
||||
self.galleryNode.pager.replaceItems(self.entries.map({
|
||||
$0.item(context: self.context, webPage: self.webPage, message: self.message, presentationData: self.presentationData, fromPlayingVideo: self.fromPlayingVideo, landscape: self.landscape, openUrl: self.innerOpenUrl, openUrlOptions: self.openUrlOptions)
|
||||
}), centralItemIndex: self.centralEntryIndex)
|
||||
@ -376,6 +403,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
|
||||
strongSelf.centralItemTitle.set(node.title())
|
||||
strongSelf.centralItemTitleView.set(node.titleView())
|
||||
strongSelf.centralItemRightBarButtonItem.set(node.rightBarButtonItem())
|
||||
strongSelf.centralItemRightBarButtonItems.set(node.rightBarButtonItems())
|
||||
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
|
||||
strongSelf.centralItemFooterContentNode.set(node.footerContent())
|
||||
}
|
||||
@ -386,6 +414,11 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
|
||||
}
|
||||
}
|
||||
|
||||
let baseNavigationController = self.baseNavigationController
|
||||
self.galleryNode.baseNavigationController = { [weak baseNavigationController] in
|
||||
return baseNavigationController
|
||||
}
|
||||
|
||||
let ready = self.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak self] _ in
|
||||
self?.didSetReady = true
|
||||
}
|
||||
@ -401,6 +434,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
|
||||
self.centralItemTitle.set(centralItemNode.title())
|
||||
self.centralItemTitleView.set(centralItemNode.titleView())
|
||||
self.centralItemRightBarButtonItem.set(centralItemNode.rightBarButtonItem())
|
||||
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
|
||||
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
|
||||
self.centralItemFooterContentNode.set(centralItemNode.footerContent())
|
||||
|
||||
|
@ -70,6 +70,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case photoPreview(PresentationTheme, Bool)
|
||||
case knockoutWallpaper(PresentationTheme, Bool)
|
||||
case alternativeFolderTabs(Bool)
|
||||
case playerEmbedding(Bool)
|
||||
case playlistPlayback(Bool)
|
||||
case videoCalls(Bool)
|
||||
case videoCallsInfo(PresentationTheme, String)
|
||||
case hostInfo(PresentationTheme, String)
|
||||
@ -85,7 +87,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.logging.rawValue
|
||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs:
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .videoCalls, .videoCallsInfo:
|
||||
return DebugControllerSection.videoExperiments.rawValue
|
||||
@ -142,14 +144,18 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 22
|
||||
case .alternativeFolderTabs:
|
||||
return 23
|
||||
case .videoCalls:
|
||||
case .playerEmbedding:
|
||||
return 24
|
||||
case .videoCallsInfo:
|
||||
case .playlistPlayback:
|
||||
return 25
|
||||
case .hostInfo:
|
||||
case .videoCalls:
|
||||
return 26
|
||||
case .versionInfo:
|
||||
case .videoCallsInfo:
|
||||
return 27
|
||||
case .hostInfo:
|
||||
return 28
|
||||
case .versionInfo:
|
||||
return 29
|
||||
}
|
||||
}
|
||||
|
||||
@ -547,6 +553,26 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .playerEmbedding(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
|
||||
settings.playerEmbedding = value
|
||||
return settings
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .playlistPlayback(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Playlist Playback", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
|
||||
settings.playlistPlayback = value
|
||||
return settings
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .videoCalls(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Experimental Feature", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
@ -602,6 +628,8 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
|
||||
//entries.append(.photoPreview(presentationData.theme, experimentalSettings.chatListPhotos))
|
||||
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
|
||||
entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom))
|
||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||
entries.append(.videoCalls(experimentalSettings.videoCalls))
|
||||
entries.append(.videoCallsInfo(presentationData.theme, "Enables experimental transmission of electromagnetic radiation synchronized with pressure waves. Needs to be enabled on both sides."))
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -18,7 +18,8 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
|
||||
maxSpeed: 0.6,
|
||||
minScale: 0.45,
|
||||
maxScale: 0.55,
|
||||
scaleSpeed: 0.2
|
||||
scaleSpeed: 0.2,
|
||||
isCircle: true
|
||||
)
|
||||
private let mediumBlob = BlobView(
|
||||
pointsCount: 8,
|
||||
@ -28,7 +29,8 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
|
||||
maxSpeed: 7,
|
||||
minScale: 0.55,
|
||||
maxScale: 0.9,
|
||||
scaleSpeed: 0.2
|
||||
scaleSpeed: 0.2,
|
||||
isCircle: false
|
||||
)
|
||||
private let bigBlob = BlobView(
|
||||
pointsCount: 8,
|
||||
@ -38,7 +40,8 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
|
||||
maxSpeed: 7,
|
||||
minScale: 0.55,
|
||||
maxScale: 1,
|
||||
scaleSpeed: 0.2
|
||||
scaleSpeed: 0.2,
|
||||
isCircle: false
|
||||
)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
@ -101,6 +104,9 @@ final class BlobView: UIView {
|
||||
let maxScale: CGFloat
|
||||
let scaleSpeed: CGFloat
|
||||
|
||||
// If true ignores randomness and pointsCount
|
||||
let isCircle: Bool
|
||||
|
||||
var level: CGFloat = 0 {
|
||||
didSet {
|
||||
speedLevel = max(level, speedLevel)
|
||||
@ -153,7 +159,8 @@ final class BlobView: UIView {
|
||||
maxSpeed: CGFloat,
|
||||
minScale: CGFloat,
|
||||
maxScale: CGFloat,
|
||||
scaleSpeed: CGFloat
|
||||
scaleSpeed: CGFloat,
|
||||
isCircle: Bool
|
||||
) {
|
||||
self.pointsCount = pointsCount
|
||||
self.minRandomness = minRandomness
|
||||
@ -163,6 +170,7 @@ final class BlobView: UIView {
|
||||
self.minScale = minScale
|
||||
self.maxScale = maxScale
|
||||
self.scaleSpeed = scaleSpeed
|
||||
self.isCircle = isCircle
|
||||
|
||||
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
||||
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
|
||||
@ -208,6 +216,8 @@ final class BlobView: UIView {
|
||||
}
|
||||
|
||||
func animateToNewShape() {
|
||||
guard !isCircle else { return }
|
||||
|
||||
if pop_animation(forKey: "blob") != nil {
|
||||
fromPoints = currentPoints
|
||||
toPoints = nil
|
||||
@ -296,6 +306,13 @@ final class BlobView: UIView {
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
shapeLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
|
||||
if isCircle {
|
||||
let halfWidth = bounds.width * 0.5
|
||||
shapeLayer.path = UIBezierPath(
|
||||
roundedRect: bounds.offsetBy(dx: -halfWidth, dy: -halfWidth),
|
||||
cornerRadius: halfWidth
|
||||
).cgPath
|
||||
}
|
||||
CATransaction.commit()
|
||||
}
|
||||
}
|
||||
|
@ -376,7 +376,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .inline:
|
||||
navigationBarPresentationData = nil
|
||||
default:
|
||||
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: false, hideBadge: false)
|
||||
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
|
||||
}
|
||||
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
|
||||
|
||||
@ -2806,9 +2806,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let navigationBarTheme: NavigationBarTheme
|
||||
|
||||
if self.hasEmbeddedTitleContent {
|
||||
navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: true, hideBadge: true)
|
||||
navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: true)
|
||||
} else {
|
||||
navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: false, hideBadge: false)
|
||||
navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
|
||||
}
|
||||
|
||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
|
||||
@ -8452,6 +8452,58 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private func openUrl(_ url: String, concealed: Bool, message: Message? = nil) {
|
||||
self.commitPurposefulAction()
|
||||
|
||||
if self.context.sharedContext.immediateExperimentalUISettings.playlistPlayback {
|
||||
if url.hasSuffix(".m3u8") {
|
||||
let navigationController = self.navigationController as? NavigationController
|
||||
|
||||
let webPage = TelegramMediaWebpage(
|
||||
webpageId: MediaId(namespace: 0, id: 0),
|
||||
content: .Loaded(TelegramMediaWebpageLoadedContent(
|
||||
url: url,
|
||||
displayUrl: url,
|
||||
hash: 0,
|
||||
type: "video",
|
||||
websiteName: nil,
|
||||
title: nil,
|
||||
text: nil,
|
||||
embedUrl: url,
|
||||
embedType: "video",
|
||||
embedSize: nil,
|
||||
duration: nil,
|
||||
author: nil,
|
||||
image: nil,
|
||||
file: nil,
|
||||
attributes: [],
|
||||
instantPage: nil
|
||||
))
|
||||
)
|
||||
let entry = InstantPageGalleryEntry(
|
||||
index: 0,
|
||||
pageId: webPage.webpageId,
|
||||
media: InstantPageMedia(
|
||||
index: 0,
|
||||
media: webPage,
|
||||
url: nil,
|
||||
caption: nil,
|
||||
credit: nil
|
||||
),
|
||||
caption: nil,
|
||||
credit: nil,
|
||||
location: nil
|
||||
)
|
||||
|
||||
let gallery = InstantPageGalleryController(context: context, webPage: webPage, entries: [entry], centralIndex: 0, replaceRootController: { [weak navigationController] controller, ready in
|
||||
if let navigationController = navigationController {
|
||||
navigationController.replaceTopController(controller, animated: false, ready: ready)
|
||||
}
|
||||
}, baseNavigationController: navigationController)
|
||||
self.present(gallery, in: .window(.root), with: InstantPageGalleryControllerPresentationArguments(transitionArguments: { entry -> GalleryTransitionArguments? in
|
||||
return nil
|
||||
}))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
openUserGeneratedUrl(context: self.context, url: url, concealed: concealed, present: { [weak self] c in
|
||||
self?.present(c, in: .window(.root))
|
||||
}, openResolved: { [weak self] resolved in
|
||||
@ -9387,7 +9439,9 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
|
||||
|
||||
func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concealed: Bool) {
|
||||
var parsedUrlValue: URL?
|
||||
if let parsed = URL(string: url) {
|
||||
if url.hasPrefix("tel:") {
|
||||
return (url, false)
|
||||
} else if let parsed = URL(string: url) {
|
||||
parsedUrlValue = parsed
|
||||
} else if let parsed = URL(string: "https://" + url) {
|
||||
parsedUrlValue = parsed
|
||||
|
@ -563,9 +563,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.addSubnode(self.navigateButtons)
|
||||
|
||||
self.addSubnode(self.navigationBarBackroundNode)
|
||||
self.navigationBarBackroundNode.isHidden = true
|
||||
self.addSubnode(self.navigationBarSeparatorNode)
|
||||
self.navigationBarSeparatorNode.isHidden = true
|
||||
if !self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding {
|
||||
self.navigationBarBackroundNode.isHidden = true
|
||||
self.navigationBarSeparatorNode.isHidden = true
|
||||
}
|
||||
|
||||
self.historyNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
@ -2691,7 +2693,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func updateEmbeddedTitlePeekContent(content: NavigationControllerDropContent?) {
|
||||
return;
|
||||
if !self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding {
|
||||
return
|
||||
}
|
||||
|
||||
guard let (_, navigationHeight) = self.validLayout else {
|
||||
return
|
||||
@ -2719,7 +2723,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
var updateHasEmbeddedTitleContent: (() -> Void)?
|
||||
|
||||
func acceptEmbeddedTitlePeekContent(content: NavigationControllerDropContent) -> Bool {
|
||||
return false;
|
||||
if !self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let (_, navigationHeight) = self.validLayout else {
|
||||
return false
|
||||
|
@ -10,12 +10,34 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
public var knockoutWallpaper: Bool
|
||||
public var foldersTabAtBottom: Bool
|
||||
public var videoCalls: Bool
|
||||
public var playerEmbedding: Bool
|
||||
public var playlistPlayback: Bool
|
||||
|
||||
public static var defaultSettings: ExperimentalUISettings {
|
||||
return ExperimentalUISettings(keepChatNavigationStack: false, skipReadHistory: false, crashOnLongQueries: false, chatListPhotos: false, knockoutWallpaper: false, foldersTabAtBottom: false, videoCalls: false)
|
||||
return ExperimentalUISettings(
|
||||
keepChatNavigationStack: false,
|
||||
skipReadHistory: false,
|
||||
crashOnLongQueries: false,
|
||||
chatListPhotos: false,
|
||||
knockoutWallpaper: false,
|
||||
foldersTabAtBottom: false,
|
||||
videoCalls: false,
|
||||
playerEmbedding: false,
|
||||
playlistPlayback: false
|
||||
)
|
||||
}
|
||||
|
||||
public init(keepChatNavigationStack: Bool, skipReadHistory: Bool, crashOnLongQueries: Bool, chatListPhotos: Bool, knockoutWallpaper: Bool, foldersTabAtBottom: Bool, videoCalls: Bool) {
|
||||
public init(
|
||||
keepChatNavigationStack: Bool,
|
||||
skipReadHistory: Bool,
|
||||
crashOnLongQueries: Bool,
|
||||
chatListPhotos: Bool,
|
||||
knockoutWallpaper: Bool,
|
||||
foldersTabAtBottom: Bool,
|
||||
videoCalls: Bool,
|
||||
playerEmbedding: Bool,
|
||||
playlistPlayback: Bool
|
||||
) {
|
||||
self.keepChatNavigationStack = keepChatNavigationStack
|
||||
self.skipReadHistory = skipReadHistory
|
||||
self.crashOnLongQueries = crashOnLongQueries
|
||||
@ -23,6 +45,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
self.knockoutWallpaper = knockoutWallpaper
|
||||
self.foldersTabAtBottom = foldersTabAtBottom
|
||||
self.videoCalls = videoCalls
|
||||
self.playerEmbedding = playerEmbedding
|
||||
self.playlistPlayback = playlistPlayback
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
@ -33,6 +57,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
self.knockoutWallpaper = decoder.decodeInt32ForKey("knockoutWallpaper", orElse: 0) != 0
|
||||
self.foldersTabAtBottom = decoder.decodeInt32ForKey("foldersTabAtBottom", orElse: 0) != 0
|
||||
self.videoCalls = decoder.decodeInt32ForKey("videoCalls", orElse: 0) != 0
|
||||
self.playerEmbedding = decoder.decodeInt32ForKey("playerEmbedding", orElse: 0) != 0
|
||||
self.playlistPlayback = decoder.decodeInt32ForKey("playlistPlayback", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
@ -43,6 +69,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
encoder.encodeInt32(self.knockoutWallpaper ? 1 : 0, forKey: "knockoutWallpaper")
|
||||
encoder.encodeInt32(self.foldersTabAtBottom ? 1 : 0, forKey: "foldersTabAtBottom")
|
||||
encoder.encodeInt32(self.videoCalls ? 1 : 0, forKey: "videoCalls")
|
||||
encoder.encodeInt32(self.playerEmbedding ? 1 : 0, forKey: "playerEmbedding")
|
||||
encoder.encodeInt32(self.playlistPlayback ? 1 : 0, forKey: "playlistPlayback")
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
|
@ -46,9 +46,32 @@ public enum PlatformVideoContentId: Hashable {
|
||||
}
|
||||
|
||||
public final class PlatformVideoContent: UniversalVideoContent {
|
||||
public enum Content {
|
||||
case file(FileMediaReference)
|
||||
case url(String)
|
||||
|
||||
var duration: Int32? {
|
||||
switch self {
|
||||
case let .file(file):
|
||||
return file.media.duration
|
||||
case .url:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var dimensions: PixelDimensions? {
|
||||
switch self {
|
||||
case let .file(file):
|
||||
return file.media.dimensions
|
||||
case .url:
|
||||
return PixelDimensions(width: 480, height: 300)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let id: AnyHashable
|
||||
let nativeId: PlatformVideoContentId
|
||||
let fileReference: FileMediaReference
|
||||
let content: Content
|
||||
public let dimensions: CGSize
|
||||
public let duration: Int32
|
||||
let streamVideo: Bool
|
||||
@ -57,12 +80,12 @@ public final class PlatformVideoContent: UniversalVideoContent {
|
||||
let baseRate: Double
|
||||
let fetchAutomatically: Bool
|
||||
|
||||
public init(id: PlatformVideoContentId, fileReference: FileMediaReference, streamVideo: Bool = false, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true) {
|
||||
public init(id: PlatformVideoContentId, content: Content, streamVideo: Bool = false, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true) {
|
||||
self.id = id
|
||||
self.nativeId = id
|
||||
self.fileReference = fileReference
|
||||
self.dimensions = fileReference.media.dimensions?.cgSize ?? CGSize(width: 128.0, height: 128.0)
|
||||
self.duration = fileReference.media.duration ?? 0
|
||||
self.content = content
|
||||
self.dimensions = self.content.dimensions?.cgSize ?? CGSize(width: 480, height: 320)
|
||||
self.duration = self.content.duration ?? 0
|
||||
self.streamVideo = streamVideo
|
||||
self.loopVideo = loopVideo
|
||||
self.enableSound = enableSound
|
||||
@ -71,15 +94,17 @@ public final class PlatformVideoContent: UniversalVideoContent {
|
||||
}
|
||||
|
||||
public func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode {
|
||||
return PlatformVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically)
|
||||
return PlatformVideoContentNode(postbox: postbox, audioSessionManager: audioSession, content: self.content, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically)
|
||||
}
|
||||
|
||||
public func isEqual(to other: UniversalVideoContent) -> Bool {
|
||||
if let other = other as? PlatformVideoContent {
|
||||
if case let .message(_, stableId, _) = self.nativeId {
|
||||
if case .message(_, stableId, _) = other.nativeId {
|
||||
if self.fileReference.media.isInstantVideo {
|
||||
return true
|
||||
if case let .file(file) = self.content {
|
||||
if file.media.isInstantVideo {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,7 +115,7 @@ public final class PlatformVideoContent: UniversalVideoContent {
|
||||
|
||||
private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoContentNode {
|
||||
private let postbox: Postbox
|
||||
private let fileReference: FileMediaReference
|
||||
private let content: PlatformVideoContent.Content
|
||||
private let approximateDuration: Double
|
||||
private let intrinsicDimensions: CGSize
|
||||
|
||||
@ -125,7 +150,7 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
|
||||
private let imageNode: TransformImageNode
|
||||
|
||||
private let playerItem: AVPlayerItem
|
||||
private var playerItem: AVPlayerItem?
|
||||
private let player: AVPlayer
|
||||
private let playerNode: ASDisplayNode
|
||||
|
||||
@ -133,6 +158,9 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
private var statusDisposable: Disposable?
|
||||
|
||||
private var didPlayToEndTimeObserver: NSObjectProtocol?
|
||||
private var didBecomeActiveObserver: NSObjectProtocol?
|
||||
private var willResignActiveObserver: NSObjectProtocol?
|
||||
private var playerItemFailedToPlayToEndTimeObserver: NSObjectProtocol?
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
||||
@ -141,16 +169,15 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool) {
|
||||
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, content: PlatformVideoContent.Content, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool) {
|
||||
self.postbox = postbox
|
||||
self.fileReference = fileReference
|
||||
self.approximateDuration = Double(fileReference.media.duration ?? 1)
|
||||
self.content = content
|
||||
self.approximateDuration = Double(content.duration ?? 1)
|
||||
self.audioSessionManager = audioSessionManager
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
self.playerItem = AVPlayerItem(url: URL(string: postbox.mediaBox.completedResourcePath(fileReference.media.resource, pathExtension: "mov") ?? "")!)
|
||||
let player = AVPlayer(playerItem: self.playerItem)
|
||||
let player = AVPlayer(playerItem: nil)
|
||||
self.player = player
|
||||
|
||||
self.playerNode = ASDisplayNode()
|
||||
@ -158,26 +185,31 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
return AVPlayerLayer(player: player)
|
||||
})
|
||||
|
||||
self.intrinsicDimensions = fileReference.media.dimensions?.cgSize ?? CGSize()
|
||||
self.intrinsicDimensions = content.dimensions?.cgSize ?? CGSize()
|
||||
|
||||
self.playerNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicDimensions)
|
||||
|
||||
super.init()
|
||||
|
||||
self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, videoReference: fileReference) |> map { [weak self] getSize, getData in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self, strongSelf.dimensions == nil {
|
||||
if let dimensions = getSize() {
|
||||
strongSelf.dimensions = dimensions
|
||||
strongSelf.dimensionsPromise.set(dimensions)
|
||||
if let size = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, transition: .immediate)
|
||||
switch content {
|
||||
case let .file(file):
|
||||
self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, videoReference: file) |> map { [weak self] getSize, getData in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self, strongSelf.dimensions == nil {
|
||||
if let dimensions = getSize() {
|
||||
strongSelf.dimensions = dimensions
|
||||
strongSelf.dimensionsPromise.set(dimensions)
|
||||
if let size = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return getData
|
||||
})
|
||||
return getData
|
||||
})
|
||||
case .url:
|
||||
break
|
||||
}
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
self.addSubnode(self.playerNode)
|
||||
@ -192,18 +224,36 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
}
|
||||
|
||||
self.player.addObserver(self, forKeyPath: "rate", options: [], context: nil)
|
||||
playerItem.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)
|
||||
playerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
|
||||
playerItem.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil)
|
||||
|
||||
self._bufferingStatus.set(.single(nil))
|
||||
|
||||
let playerItem: AVPlayerItem
|
||||
switch content {
|
||||
case let .file(file):
|
||||
playerItem = AVPlayerItem(url: URL(string: postbox.mediaBox.completedResourcePath(file.media.resource, pathExtension: "mov") ?? "")!)
|
||||
case let .url(url):
|
||||
playerItem = AVPlayerItem(url: URL(string: url)!)
|
||||
}
|
||||
self.setPlayerItem(playerItem)
|
||||
|
||||
self.didBecomeActiveObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil, using: { [weak self] _ in
|
||||
guard let strongSelf = self, let layer = strongSelf.playerNode.layer as? AVPlayerLayer else {
|
||||
return
|
||||
}
|
||||
layer.player = strongSelf.player
|
||||
})
|
||||
self.willResignActiveObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil, using: { [weak self] _ in
|
||||
guard let strongSelf = self, let layer = strongSelf.playerNode.layer as? AVPlayerLayer else {
|
||||
return
|
||||
}
|
||||
layer.player = nil
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.player.removeObserver(self, forKeyPath: "rate")
|
||||
self.playerItem.removeObserver(self, forKeyPath: "playbackBufferEmpty")
|
||||
self.playerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
|
||||
self.playerItem.removeObserver(self, forKeyPath: "playbackBufferFull")
|
||||
|
||||
self.setPlayerItem(nil)
|
||||
|
||||
self.audioSessionDisposable.dispose()
|
||||
|
||||
@ -213,12 +263,57 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver {
|
||||
NotificationCenter.default.removeObserver(didPlayToEndTimeObserver)
|
||||
}
|
||||
if let didBecomeActiveObserver = self.didBecomeActiveObserver {
|
||||
NotificationCenter.default.removeObserver(didBecomeActiveObserver)
|
||||
}
|
||||
if let willResignActiveObserver = self.willResignActiveObserver {
|
||||
NotificationCenter.default.removeObserver(willResignActiveObserver)
|
||||
}
|
||||
}
|
||||
|
||||
private func setPlayerItem(_ item: AVPlayerItem?) {
|
||||
if let playerItem = self.playerItem {
|
||||
playerItem.removeObserver(self, forKeyPath: "playbackBufferEmpty")
|
||||
playerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
|
||||
playerItem.removeObserver(self, forKeyPath: "playbackBufferFull")
|
||||
playerItem.removeObserver(self, forKeyPath: "status")
|
||||
if let playerItemFailedToPlayToEndTimeObserver = self.playerItemFailedToPlayToEndTimeObserver {
|
||||
NotificationCenter.default.removeObserver(playerItemFailedToPlayToEndTimeObserver)
|
||||
self.playerItemFailedToPlayToEndTimeObserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
self.playerItem = item
|
||||
|
||||
if let playerItem = self.playerItem {
|
||||
playerItem.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)
|
||||
playerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
|
||||
playerItem.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil)
|
||||
playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)
|
||||
self.playerItemFailedToPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: playerItem, queue: OperationQueue.main, using: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch strongSelf.content {
|
||||
case .file:
|
||||
break
|
||||
case let .url(url):
|
||||
let updatedPlayerItem = AVPlayerItem(url: URL(string: url)!)
|
||||
strongSelf.setPlayerItem(updatedPlayerItem)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.player.replaceCurrentItem(with: self.playerItem)
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
if keyPath == "rate" {
|
||||
let isPlaying = !self.player.rate.isZero
|
||||
let status: MediaPlayerPlaybackStatus
|
||||
if isPlaying {
|
||||
self.isBuffering = false
|
||||
}
|
||||
if self.isBuffering {
|
||||
status = .buffering(initial: false, whilePlaying: isPlaying)
|
||||
} else {
|
||||
@ -248,6 +343,21 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
}
|
||||
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: status, soundEnabled: true)
|
||||
self._status.set(self.statusValue)
|
||||
} else if keyPath == "status" {
|
||||
if let playerItem = self.playerItem, false {
|
||||
switch playerItem.status {
|
||||
case .failed:
|
||||
switch self.content {
|
||||
case .file:
|
||||
break
|
||||
case let .url(url):
|
||||
let updatedPlayerItem = AVPlayerItem(url: URL(string: url)!)
|
||||
self.setPlayerItem(updatedPlayerItem)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user