Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-07-01 04:43:46 +03:00
commit a4ea5e5642
10 changed files with 394 additions and 71 deletions

View File

@ -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") { 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) 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 { } 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)
} }
} }

View File

@ -18,7 +18,7 @@ import AppBundle
public enum UniversalVideoGalleryItemContentInfo { public enum UniversalVideoGalleryItemContentInfo {
case message(Message) case message(Message)
case webPage(TelegramMediaWebpage, Media) case webPage(TelegramMediaWebpage, Media, ((@escaping () -> GalleryTransitionArguments?, NavigationController?, (ViewController, Any?) -> Void) -> Void)?)
} }
public class UniversalVideoGalleryItem: GalleryItem { 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)) { if let item = ChatMediaGalleryThumbnailItem(account: self.context.account, mediaReference: .webPage(webPage: WebpageReference(webPage), media: file)) {
return (0, item) return (0, item)
} }
@ -470,6 +470,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
var disablePictureInPicture = false var disablePictureInPicture = false
var disablePlayerControls = false var disablePlayerControls = false
var forceEnablePiP = false
var isAnimated = false var isAnimated = false
if let content = item.content as? NativeVideoContent { if let content = item.content as? NativeVideoContent {
isAnimated = content.fileReference.media.isAnimated isAnimated = content.fileReference.media.isAnimated
@ -487,6 +488,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
default: default:
break break
} }
} else if let _ = item.content as? PlatformVideoContent {
disablePlayerControls = true
forceEnablePiP = true
} }
if let videoNode = self.videoNode { if let videoNode = self.videoNode {
@ -511,7 +515,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
strongSelf.playOnContentOwnership = false strongSelf.playOnContentOwnership = false
strongSelf.initiallyActivated = true strongSelf.initiallyActivated = true
strongSelf.skipInitialPause = 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)) let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
barButtonItems.append(rightBarButtonItem) 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)) let rightBarButtonItem = UIBarButtonItem(image: pictureInPictureButtonImage, style: .plain, target: self, action: #selector(self.pictureInPictureButtonPressed))
barButtonItems.append(rightBarButtonItem) barButtonItems.append(rightBarButtonItem)
self.hasPictureInPicture = true self.hasPictureInPicture = true
@ -725,9 +733,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
switch contentInfo { switch contentInfo {
case let .message(message): case let .message(message):
self.footerContentNode.setMessage(message) self.footerContentNode.setMessage(message)
case let .webPage(webPage, media): case let .webPage(webPage, media, _):
self.footerContentNode.setWebPage(webPage, media: media) self.footerContentNode.setWebPage(webPage, media: media)
break
} }
} }
self.footerContentNode.setup(origin: item.originData, caption: item.caption) self.footerContentNode.setup(origin: item.originData, caption: item.caption)
@ -757,7 +764,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
private func shouldAutoplayOnCentrality() -> Bool { private func shouldAutoplayOnCentrality() -> Bool {
// !self.initiallyActivated
if let item = self.item, let content = item.content as? NativeVideoContent { if let item = self.item, let content = item.content as? NativeVideoContent {
var isLocal = false var isLocal = false
if let fetchStatus = self.fetchStatus, case .Local = fetchStatus { if let fetchStatus = self.fetchStatus, case .Local = fetchStatus {
@ -772,6 +778,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if isLocal || isStreamable { if isLocal || isStreamable {
return true return true
} }
} else if let item = self.item, let _ = item.content as? PlatformVideoContent {
return true
} }
return false return false
} }
@ -1335,8 +1343,27 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
return nil return nil
})) }))
case .webPage: case let .webPage(_, _, expandFromPip):
break 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) { if customUnembedWhenPortrait(overlayNode) {
@ -1398,8 +1425,27 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
return nil return nil
})) }))
case .webPage: case let .webPage(_, _, expandFromPip):
break 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) context.sharedContext.mediaManager.setOverlayVideoNode(overlayNode)

View File

@ -113,7 +113,7 @@ public struct InstantPageGalleryEntry: Equatable {
nativeId = .instantPage(self.pageId, file.fileId) 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 { } else {
var representations: [TelegramMediaImageRepresentation] = [] var representations: [TelegramMediaImageRepresentation] = []
representations.append(contentsOf: file.previewRepresentations) 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) 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 { } else if let embedWebpage = self.media.media as? TelegramMediaWebpage, case let .Loaded(webpageContent) = embedWebpage.content {
if let content = WebEmbedVideoContent(webPage: embedWebpage, webpageContent: webpageContent) { if webpageContent.url.hasSuffix(".m3u8") {
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 }) 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 { } 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 { } else {
preconditionFailure() preconditionFailure()
@ -169,6 +183,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
private let centralItemTitle = Promise<String>() private let centralItemTitle = Promise<String>()
private let centralItemTitleView = Promise<UIView?>() private let centralItemTitleView = Promise<UIView?>()
private let centralItemRightBarButtonItem = Promise<UIBarButtonItem?>() private let centralItemRightBarButtonItem = Promise<UIBarButtonItem?>()
private let centralItemRightBarButtonItems = Promise<[UIBarButtonItem]?>(nil)
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>() private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>() private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>()
private let centralItemAttributesDisposable = DisposableSet(); private let centralItemAttributesDisposable = DisposableSet();
@ -239,8 +254,15 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
self?.navigationItem.titleView = titleView self?.navigationItem.titleView = titleView
})) }))
self.centralItemAttributesDisposable.add(self.centralItemRightBarButtonItem.get().start(next: { [weak self] rightBarButtonItem in self.centralItemAttributesDisposable.add(combineLatest(self.centralItemRightBarButtonItem.get(), self.centralItemRightBarButtonItems.get()).start(next: { [weak self] rightBarButtonItem, rightBarButtonItems in
self?.navigationItem.rightBarButtonItem = rightBarButtonItem 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 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?.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({ 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) $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) }), centralItemIndex: self.centralEntryIndex)
@ -376,6 +403,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
strongSelf.centralItemTitle.set(node.title()) strongSelf.centralItemTitle.set(node.title())
strongSelf.centralItemTitleView.set(node.titleView()) strongSelf.centralItemTitleView.set(node.titleView())
strongSelf.centralItemRightBarButtonItem.set(node.rightBarButtonItem()) strongSelf.centralItemRightBarButtonItem.set(node.rightBarButtonItem())
strongSelf.centralItemRightBarButtonItems.set(node.rightBarButtonItems())
strongSelf.centralItemNavigationStyle.set(node.navigationStyle()) strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
strongSelf.centralItemFooterContentNode.set(node.footerContent()) 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 let ready = self.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak self] _ in
self?.didSetReady = true self?.didSetReady = true
} }
@ -401,6 +434,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
self.centralItemTitle.set(centralItemNode.title()) self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView()) self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemRightBarButtonItem.set(centralItemNode.rightBarButtonItem()) self.centralItemRightBarButtonItem.set(centralItemNode.rightBarButtonItem())
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle()) self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
self.centralItemFooterContentNode.set(centralItemNode.footerContent()) self.centralItemFooterContentNode.set(centralItemNode.footerContent())

View File

@ -70,6 +70,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case photoPreview(PresentationTheme, Bool) case photoPreview(PresentationTheme, Bool)
case knockoutWallpaper(PresentationTheme, Bool) case knockoutWallpaper(PresentationTheme, Bool)
case alternativeFolderTabs(Bool) case alternativeFolderTabs(Bool)
case playerEmbedding(Bool)
case playlistPlayback(Bool)
case videoCalls(Bool) case videoCalls(Bool)
case videoCallsInfo(PresentationTheme, String) case videoCallsInfo(PresentationTheme, String)
case hostInfo(PresentationTheme, String) case hostInfo(PresentationTheme, String)
@ -85,7 +87,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue 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 return DebugControllerSection.experiments.rawValue
case .videoCalls, .videoCallsInfo: case .videoCalls, .videoCallsInfo:
return DebugControllerSection.videoExperiments.rawValue return DebugControllerSection.videoExperiments.rawValue
@ -142,14 +144,18 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 22 return 22
case .alternativeFolderTabs: case .alternativeFolderTabs:
return 23 return 23
case .videoCalls: case .playerEmbedding:
return 24 return 24
case .videoCallsInfo: case .playlistPlayback:
return 25 return 25
case .hostInfo: case .videoCalls:
return 26 return 26
case .versionInfo: case .videoCallsInfo:
return 27 return 27
case .hostInfo:
return 28
case .versionInfo:
return 29
} }
} }
@ -547,6 +553,26 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).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): case let .videoCalls(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Experimental Feature", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: "Experimental Feature", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction 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(.photoPreview(presentationData.theme, experimentalSettings.chatListPhotos))
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper)) entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom)) entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
entries.append(.videoCalls(experimentalSettings.videoCalls)) 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.")) 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

View File

@ -18,7 +18,8 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
maxSpeed: 0.6, maxSpeed: 0.6,
minScale: 0.45, minScale: 0.45,
maxScale: 0.55, maxScale: 0.55,
scaleSpeed: 0.2 scaleSpeed: 0.2,
isCircle: true
) )
private let mediumBlob = BlobView( private let mediumBlob = BlobView(
pointsCount: 8, pointsCount: 8,
@ -28,7 +29,8 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
maxSpeed: 7, maxSpeed: 7,
minScale: 0.55, minScale: 0.55,
maxScale: 0.9, maxScale: 0.9,
scaleSpeed: 0.2 scaleSpeed: 0.2,
isCircle: false
) )
private let bigBlob = BlobView( private let bigBlob = BlobView(
pointsCount: 8, pointsCount: 8,
@ -38,7 +40,8 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
maxSpeed: 7, maxSpeed: 7,
minScale: 0.55, minScale: 0.55,
maxScale: 1, maxScale: 1,
scaleSpeed: 0.2 scaleSpeed: 0.2,
isCircle: false
) )
override init(frame: CGRect) { override init(frame: CGRect) {
@ -101,6 +104,9 @@ final class BlobView: UIView {
let maxScale: CGFloat let maxScale: CGFloat
let scaleSpeed: CGFloat let scaleSpeed: CGFloat
// If true ignores randomness and pointsCount
let isCircle: Bool
var level: CGFloat = 0 { var level: CGFloat = 0 {
didSet { didSet {
speedLevel = max(level, speedLevel) speedLevel = max(level, speedLevel)
@ -153,7 +159,8 @@ final class BlobView: UIView {
maxSpeed: CGFloat, maxSpeed: CGFloat,
minScale: CGFloat, minScale: CGFloat,
maxScale: CGFloat, maxScale: CGFloat,
scaleSpeed: CGFloat scaleSpeed: CGFloat,
isCircle: Bool
) { ) {
self.pointsCount = pointsCount self.pointsCount = pointsCount
self.minRandomness = minRandomness self.minRandomness = minRandomness
@ -163,6 +170,7 @@ final class BlobView: UIView {
self.minScale = minScale self.minScale = minScale
self.maxScale = maxScale self.maxScale = maxScale
self.scaleSpeed = scaleSpeed self.scaleSpeed = scaleSpeed
self.isCircle = isCircle
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount) let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2 self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
@ -208,6 +216,8 @@ final class BlobView: UIView {
} }
func animateToNewShape() { func animateToNewShape() {
guard !isCircle else { return }
if pop_animation(forKey: "blob") != nil { if pop_animation(forKey: "blob") != nil {
fromPoints = currentPoints fromPoints = currentPoints
toPoints = nil toPoints = nil
@ -296,6 +306,13 @@ final class BlobView: UIView {
CATransaction.begin() CATransaction.begin()
CATransaction.setDisableActions(true) CATransaction.setDisableActions(true)
shapeLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) 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() CATransaction.commit()
} }
} }

View File

@ -376,7 +376,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .inline: case .inline:
navigationBarPresentationData = nil navigationBarPresentationData = nil
default: 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) super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
@ -2806,9 +2806,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let navigationBarTheme: NavigationBarTheme let navigationBarTheme: NavigationBarTheme
if self.hasEmbeddedTitleContent { 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 { } 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))) 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) { private func openUrl(_ url: String, concealed: Bool, message: Message? = nil) {
self.commitPurposefulAction() 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 openUserGeneratedUrl(context: self.context, url: url, concealed: concealed, present: { [weak self] c in
self?.present(c, in: .window(.root)) self?.present(c, in: .window(.root))
}, openResolved: { [weak self] resolved in }, openResolved: { [weak self] resolved in
@ -9387,7 +9439,9 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concealed: Bool) { func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concealed: Bool) {
var parsedUrlValue: URL? 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 parsedUrlValue = parsed
} else if let parsed = URL(string: "https://" + url) { } else if let parsed = URL(string: "https://" + url) {
parsedUrlValue = parsed parsedUrlValue = parsed

View File

@ -563,9 +563,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.addSubnode(self.navigateButtons) self.addSubnode(self.navigateButtons)
self.addSubnode(self.navigationBarBackroundNode) self.addSubnode(self.navigationBarBackroundNode)
self.navigationBarBackroundNode.isHidden = true
self.addSubnode(self.navigationBarSeparatorNode) 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(_:)))) self.historyNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
@ -2691,7 +2693,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
func updateEmbeddedTitlePeekContent(content: NavigationControllerDropContent?) { func updateEmbeddedTitlePeekContent(content: NavigationControllerDropContent?) {
return; if !self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding {
return
}
guard let (_, navigationHeight) = self.validLayout else { guard let (_, navigationHeight) = self.validLayout else {
return return
@ -2719,7 +2723,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
var updateHasEmbeddedTitleContent: (() -> Void)? var updateHasEmbeddedTitleContent: (() -> Void)?
func acceptEmbeddedTitlePeekContent(content: NavigationControllerDropContent) -> Bool { func acceptEmbeddedTitlePeekContent(content: NavigationControllerDropContent) -> Bool {
return false; if !self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding {
return false
}
guard let (_, navigationHeight) = self.validLayout else { guard let (_, navigationHeight) = self.validLayout else {
return false return false

View File

@ -10,12 +10,34 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
public var knockoutWallpaper: Bool public var knockoutWallpaper: Bool
public var foldersTabAtBottom: Bool public var foldersTabAtBottom: Bool
public var videoCalls: Bool public var videoCalls: Bool
public var playerEmbedding: Bool
public var playlistPlayback: Bool
public static var defaultSettings: ExperimentalUISettings { 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.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory self.skipReadHistory = skipReadHistory
self.crashOnLongQueries = crashOnLongQueries self.crashOnLongQueries = crashOnLongQueries
@ -23,6 +45,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
self.knockoutWallpaper = knockoutWallpaper self.knockoutWallpaper = knockoutWallpaper
self.foldersTabAtBottom = foldersTabAtBottom self.foldersTabAtBottom = foldersTabAtBottom
self.videoCalls = videoCalls self.videoCalls = videoCalls
self.playerEmbedding = playerEmbedding
self.playlistPlayback = playlistPlayback
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
@ -33,6 +57,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
self.knockoutWallpaper = decoder.decodeInt32ForKey("knockoutWallpaper", orElse: 0) != 0 self.knockoutWallpaper = decoder.decodeInt32ForKey("knockoutWallpaper", orElse: 0) != 0
self.foldersTabAtBottom = decoder.decodeInt32ForKey("foldersTabAtBottom", orElse: 0) != 0 self.foldersTabAtBottom = decoder.decodeInt32ForKey("foldersTabAtBottom", orElse: 0) != 0
self.videoCalls = decoder.decodeInt32ForKey("videoCalls", 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) { 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.knockoutWallpaper ? 1 : 0, forKey: "knockoutWallpaper")
encoder.encodeInt32(self.foldersTabAtBottom ? 1 : 0, forKey: "foldersTabAtBottom") encoder.encodeInt32(self.foldersTabAtBottom ? 1 : 0, forKey: "foldersTabAtBottom")
encoder.encodeInt32(self.videoCalls ? 1 : 0, forKey: "videoCalls") 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 { public func isEqual(to: PreferencesEntry) -> Bool {

View File

@ -46,9 +46,32 @@ public enum PlatformVideoContentId: Hashable {
} }
public final class PlatformVideoContent: UniversalVideoContent { 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 public let id: AnyHashable
let nativeId: PlatformVideoContentId let nativeId: PlatformVideoContentId
let fileReference: FileMediaReference let content: Content
public let dimensions: CGSize public let dimensions: CGSize
public let duration: Int32 public let duration: Int32
let streamVideo: Bool let streamVideo: Bool
@ -57,12 +80,12 @@ public final class PlatformVideoContent: UniversalVideoContent {
let baseRate: Double let baseRate: Double
let fetchAutomatically: Bool 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.id = id
self.nativeId = id self.nativeId = id
self.fileReference = fileReference self.content = content
self.dimensions = fileReference.media.dimensions?.cgSize ?? CGSize(width: 128.0, height: 128.0) self.dimensions = self.content.dimensions?.cgSize ?? CGSize(width: 480, height: 320)
self.duration = fileReference.media.duration ?? 0 self.duration = self.content.duration ?? 0
self.streamVideo = streamVideo self.streamVideo = streamVideo
self.loopVideo = loopVideo self.loopVideo = loopVideo
self.enableSound = enableSound self.enableSound = enableSound
@ -71,15 +94,17 @@ public final class PlatformVideoContent: UniversalVideoContent {
} }
public func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { 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 { public func isEqual(to other: UniversalVideoContent) -> Bool {
if let other = other as? PlatformVideoContent { if let other = other as? PlatformVideoContent {
if case let .message(_, stableId, _) = self.nativeId { if case let .message(_, stableId, _) = self.nativeId {
if case .message(_, stableId, _) = other.nativeId { if case .message(_, stableId, _) = other.nativeId {
if self.fileReference.media.isInstantVideo { if case let .file(file) = self.content {
return true if file.media.isInstantVideo {
return true
}
} }
} }
} }
@ -90,7 +115,7 @@ public final class PlatformVideoContent: UniversalVideoContent {
private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoContentNode { private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoContentNode {
private let postbox: Postbox private let postbox: Postbox
private let fileReference: FileMediaReference private let content: PlatformVideoContent.Content
private let approximateDuration: Double private let approximateDuration: Double
private let intrinsicDimensions: CGSize private let intrinsicDimensions: CGSize
@ -125,7 +150,7 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private let playerItem: AVPlayerItem private var playerItem: AVPlayerItem?
private let player: AVPlayer private let player: AVPlayer
private let playerNode: ASDisplayNode private let playerNode: ASDisplayNode
@ -133,6 +158,9 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
private var statusDisposable: Disposable? private var statusDisposable: Disposable?
private var didPlayToEndTimeObserver: NSObjectProtocol? private var didPlayToEndTimeObserver: NSObjectProtocol?
private var didBecomeActiveObserver: NSObjectProtocol?
private var willResignActiveObserver: NSObjectProtocol?
private var playerItemFailedToPlayToEndTimeObserver: NSObjectProtocol?
private let fetchDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable()
@ -141,16 +169,15 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
private var validLayout: CGSize? 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.postbox = postbox
self.fileReference = fileReference self.content = content
self.approximateDuration = Double(fileReference.media.duration ?? 1) self.approximateDuration = Double(content.duration ?? 1)
self.audioSessionManager = audioSessionManager self.audioSessionManager = audioSessionManager
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.playerItem = AVPlayerItem(url: URL(string: postbox.mediaBox.completedResourcePath(fileReference.media.resource, pathExtension: "mov") ?? "")!) let player = AVPlayer(playerItem: nil)
let player = AVPlayer(playerItem: self.playerItem)
self.player = player self.player = player
self.playerNode = ASDisplayNode() self.playerNode = ASDisplayNode()
@ -158,26 +185,31 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
return AVPlayerLayer(player: player) 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) self.playerNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicDimensions)
super.init() super.init()
self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, videoReference: fileReference) |> map { [weak self] getSize, getData in switch content {
Queue.mainQueue().async { case let .file(file):
if let strongSelf = self, strongSelf.dimensions == nil { self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, videoReference: file) |> map { [weak self] getSize, getData in
if let dimensions = getSize() { Queue.mainQueue().async {
strongSelf.dimensions = dimensions if let strongSelf = self, strongSelf.dimensions == nil {
strongSelf.dimensionsPromise.set(dimensions) if let dimensions = getSize() {
if let size = strongSelf.validLayout { strongSelf.dimensions = dimensions
strongSelf.updateLayout(size: size, transition: .immediate) 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.imageNode)
self.addSubnode(self.playerNode) self.addSubnode(self.playerNode)
@ -192,18 +224,36 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
} }
self.player.addObserver(self, forKeyPath: "rate", options: [], context: nil) 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)) 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 { deinit {
self.player.removeObserver(self, forKeyPath: "rate") self.player.removeObserver(self, forKeyPath: "rate")
self.playerItem.removeObserver(self, forKeyPath: "playbackBufferEmpty")
self.playerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp") self.setPlayerItem(nil)
self.playerItem.removeObserver(self, forKeyPath: "playbackBufferFull")
self.audioSessionDisposable.dispose() self.audioSessionDisposable.dispose()
@ -213,12 +263,57 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver { if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver {
NotificationCenter.default.removeObserver(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?) { override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate" { if keyPath == "rate" {
let isPlaying = !self.player.rate.isZero let isPlaying = !self.player.rate.isZero
let status: MediaPlayerPlaybackStatus let status: MediaPlayerPlaybackStatus
if isPlaying {
self.isBuffering = false
}
if self.isBuffering { if self.isBuffering {
status = .buffering(initial: false, whilePlaying: isPlaying) status = .buffering(initial: false, whilePlaying: isPlaying)
} else { } 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.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) 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
}
}
} }
} }