mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-13 06:49:23 +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") {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,10 +515,14 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
strongSelf.playOnContentOwnership = false
|
strongSelf.playOnContentOwnership = false
|
||||||
strongSelf.initiallyActivated = true
|
strongSelf.initiallyActivated = true
|
||||||
strongSelf.skipInitialPause = true
|
strongSelf.skipInitialPause = true
|
||||||
|
if let item = strongSelf.item, let _ = item.content as? PlatformVideoContent {
|
||||||
|
strongSelf.videoNode?.play()
|
||||||
|
} else {
|
||||||
strongSelf.videoNode?.playOnceWithSound(playAndRecord: false, actionAtEnd: isAnimated ? .loop : .stop)
|
strongSelf.videoNode?.playOnceWithSound(playAndRecord: false, actionAtEnd: isAnimated ? .loop : .stop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.videoNode = videoNode
|
self.videoNode = videoNode
|
||||||
videoNode.isUserInteractionEnabled = disablePlayerControls
|
videoNode.isUserInteractionEnabled = disablePlayerControls
|
||||||
videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335)
|
videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335)
|
||||||
@ -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)
|
||||||
|
@ -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,11 +124,25 @@ 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 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 {
|
||||||
if let content = WebEmbedVideoContent(webPage: embedWebpage, webpageContent: webpageContent) {
|
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 })
|
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 {
|
} else {
|
||||||
preconditionFailure()
|
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
|
||||||
|
if let rightBarButtonItem = rightBarButtonItem {
|
||||||
self?.navigationItem.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())
|
||||||
|
|
||||||
|
@ -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
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
if !self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding {
|
||||||
|
self.navigationBarBackroundNode.isHidden = true
|
||||||
self.navigationBarSeparatorNode.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
|
||||||
|
@ -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 {
|
||||||
|
@ -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,26 +94,28 @@ 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 {
|
||||||
|
if file.media.isInstantVideo {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,13 +185,15 @@ 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 {
|
||||||
|
case let .file(file):
|
||||||
|
self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, videoReference: file) |> map { [weak self] getSize, getData in
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
if let strongSelf = self, strongSelf.dimensions == nil {
|
if let strongSelf = self, strongSelf.dimensions == nil {
|
||||||
if let dimensions = getSize() {
|
if let dimensions = getSize() {
|
||||||
@ -178,6 +207,9 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
|||||||
}
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user