Various improvements

This commit is contained in:
Ilya Laktyushin 2022-07-08 14:30:49 +02:00
parent 0c2f2ec1b7
commit 9d41d0f110
13 changed files with 510 additions and 123 deletions

Binary file not shown.

View File

@ -7831,6 +7831,9 @@ Sorry for the inconvenience.";
"Notification.PremiumGift.Sent" = "%1$@ sent you a gift for %2$@"; "Notification.PremiumGift.Sent" = "%1$@ sent you a gift for %2$@";
"Notification.PremiumGift.SentYou" = "You sent a gift for %@"; "Notification.PremiumGift.SentYou" = "You sent a gift for %@";
"Notification.PremiumGift.Months_1" = "%@ month";
"Notification.PremiumGift.Months_any" = "%@ months";
"Notification.PremiumGift.Title" = "Telegram Premium"; "Notification.PremiumGift.Title" = "Telegram Premium";
"Notification.PremiumGift.Subtitle" = "for %@"; "Notification.PremiumGift.Subtitle" = "for %@";
"Notification.PremiumGift.View" = "View"; "Notification.PremiumGift.View" = "View";

View File

@ -147,10 +147,15 @@ private let moreButtonImage = generateTintedImage(image: UIImage(bundleImageName
private let placeholderFont = Font.regular(16.0) private let placeholderFont = Font.regular(16.0)
private final class UniversalVideoGalleryItemPictureInPictureNode: ASDisplayNode { private final class UniversalVideoGalleryItemPictureInPictureNode: ASDisplayNode {
enum Mode {
case pictureInPicture
case airplay
}
private let iconNode: ASImageNode private let iconNode: ASImageNode
private let textNode: ASTextNode private let textNode: ASTextNode
init(strings: PresentationStrings) { init(strings: PresentationStrings, mode: Mode) {
self.iconNode = ASImageNode() self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true self.iconNode.isLayerBacked = true
self.iconNode.displayWithoutProcessing = true self.iconNode.displayWithoutProcessing = true
@ -160,10 +165,20 @@ private final class UniversalVideoGalleryItemPictureInPictureNode: ASDisplayNode
self.textNode = ASTextNode() self.textNode = ASTextNode()
self.textNode.isUserInteractionEnabled = false self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: strings.Embed_PlayingInPIP, font: placeholderFont, textColor: UIColor(rgb: 0x8e8e93))
let text: String
switch mode {
case .pictureInPicture:
text = strings.Embed_PlayingInPIP
case .airplay:
text = strings.Gallery_AirPlayPlaceholder
}
self.textNode.attributedText = NSAttributedString(string: text, font: placeholderFont, textColor: UIColor(rgb: 0x8e8e93))
super.init() super.init()
self.backgroundColor = UIColor(rgb: 0x333335)
self.addSubnode(self.iconNode) self.addSubnode(self.iconNode)
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
} }
@ -975,7 +990,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let pictureInPictureNode = self.pictureInPictureNode { if let pictureInPictureNode = self.pictureInPictureNode {
if let item = self.item { if let item = self.item {
let placeholderSize = item.content.dimensions.fitted(layout.size) var placeholderSize = item.content.dimensions.fitted(layout.size)
placeholderSize.height += 2.0
transition.updateFrame(node: pictureInPictureNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - placeholderSize.width) / 2.0), y: floor((layout.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) transition.updateFrame(node: pictureInPictureNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - placeholderSize.width) / 2.0), y: floor((layout.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
pictureInPictureNode.updateLayout(placeholderSize, transition: transition) pictureInPictureNode.updateLayout(placeholderSize, transition: transition)
} }
@ -1144,11 +1160,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.videoNode = videoNode self.videoNode = videoNode
self.videoNodeUserInteractionEnabled = disablePlayerControls || forceEnableUserInteraction self.videoNodeUserInteractionEnabled = disablePlayerControls || forceEnableUserInteraction
videoNode.isUserInteractionEnabled = disablePlayerControls || forceEnableUserInteraction videoNode.isUserInteractionEnabled = disablePlayerControls || forceEnableUserInteraction
videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335) videoNode.backgroundColor = UIColor.black
if item.fromPlayingVideo { if item.fromPlayingVideo {
videoNode.canAttachContent = false videoNode.canAttachContent = false
} else { } else {
self.updateDisplayPlaceholder(!videoNode.ownsContentNode) self.updateDisplayPlaceholder()
} }
scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in
@ -1492,21 +1508,25 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.videoNode?.notifyPlaybackControlsHidden(!isVisible) self.videoNode?.notifyPlaybackControlsHidden(!isVisible)
} }
private func updateDisplayPlaceholder() {
self.updateDisplayPlaceholder(!(self.videoNode?.ownsContentNode ?? true) || self.isAirPlayActive)
}
private func updateDisplayPlaceholder(_ displayPlaceholder: Bool) { private func updateDisplayPlaceholder(_ displayPlaceholder: Bool) {
if displayPlaceholder && !self.disablePictureInPicturePlaceholder { if displayPlaceholder && !self.disablePictureInPicturePlaceholder {
if self.pictureInPictureNode == nil { if self.pictureInPictureNode == nil {
let pictureInPictureNode = UniversalVideoGalleryItemPictureInPictureNode(strings: self.presentationData.strings) let pictureInPictureNode = UniversalVideoGalleryItemPictureInPictureNode(strings: self.presentationData.strings, mode: self.isAirPlayActive ? .airplay : .pictureInPicture)
pictureInPictureNode.isUserInteractionEnabled = false pictureInPictureNode.isUserInteractionEnabled = false
self.pictureInPictureNode = pictureInPictureNode self.pictureInPictureNode = pictureInPictureNode
self.insertSubnode(pictureInPictureNode, aboveSubnode: self.scrollNode) self.insertSubnode(pictureInPictureNode, aboveSubnode: self.scrollNode)
if let validLayout = self.validLayout { if let validLayout = self.validLayout {
if let item = self.item { if let item = self.item {
let placeholderSize = item.content.dimensions.fitted(validLayout.0.size) var placeholderSize = item.content.dimensions.fitted(validLayout.0.size)
pictureInPictureNode.frame = CGRect(origin: CGPoint(x: floor((validLayout.0.size.width - placeholderSize.width) / 2.0), y: floor((validLayout.0.size.height - placeholderSize.height) / 2.0)), size: placeholderSize) placeholderSize.height += 2.0
pictureInPictureNode.frame = CGRect(origin: CGPoint(x: floor((validLayout.0.size.width - placeholderSize.width) / 2.0), y: floorToScreenPixels((validLayout.0.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)
pictureInPictureNode.updateLayout(placeholderSize, transition: .immediate) pictureInPictureNode.updateLayout(placeholderSize, transition: .immediate)
} }
} }
self.videoNode?.backgroundColor = UIColor(rgb: 0x333335)
} }
} else if let pictureInPictureNode = self.pictureInPictureNode { } else if let pictureInPictureNode = self.pictureInPictureNode {
self.pictureInPictureNode = nil self.pictureInPictureNode = nil
@ -1602,10 +1622,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} else { } else {
videoNode.continuePlayingWithoutSound() videoNode.continuePlayingWithoutSound()
} }
self.updateDisplayPlaceholder(!videoNode.ownsContentNode) self.updateDisplayPlaceholder()
} else if !item.fromPlayingVideo { } else if !item.fromPlayingVideo {
videoNode.canAttachContent = isVisible videoNode.canAttachContent = isVisible
self.updateDisplayPlaceholder(!videoNode.ownsContentNode) self.updateDisplayPlaceholder()
} }
if self.shouldAutoplayOnCentrality() { if self.shouldAutoplayOnCentrality() {
self.hideStatusNodeUntilCentrality = true self.hideStatusNodeUntilCentrality = true
@ -1690,7 +1710,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
videoNode.canAttachContent = true videoNode.canAttachContent = true
self.updateDisplayPlaceholder(!videoNode.ownsContentNode) self.updateDisplayPlaceholder()
self.context.sharedContext.mediaManager.setOverlayVideoNode(nil) self.context.sharedContext.mediaManager.setOverlayVideoNode(nil)
} else { } else {
@ -1768,7 +1788,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if self.item?.fromPlayingVideo ?? false { if self.item?.fromPlayingVideo ?? false {
Queue.mainQueue().after(0.001) { Queue.mainQueue().after(0.001) {
videoNode.canAttachContent = true videoNode.canAttachContent = true
self.updateDisplayPlaceholder(!videoNode.ownsContentNode) self.updateDisplayPlaceholder()
} }
} }
@ -2457,6 +2477,16 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
c.setItems(strongSelf.contextMenuSpeedItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) c.setItems(strongSelf.contextMenuSpeedItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
}))) })))
if #available(iOS 11.0, *) {
items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let strongSelf = self else {
return
}
strongSelf.beginAirPlaySetup()
})))
}
if let (message, _, _) = strongSelf.contentInfo() { if let (message, _, _) = strongSelf.contentInfo() {
for media in message.media { for media in message.media {
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
@ -2578,63 +2608,85 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return items return items
} }
} }
private var isAirPlayActive = false
private var externalVideoPlayer: ExternalVideoPlayer?
func beginAirPlaySetup() {
guard let content = self.item?.content as? NativeVideoContent else {
return
}
if #available(iOS 11.0, *) {
self.externalVideoPlayer = ExternalVideoPlayer(context: self.context, content: content)
self.externalVideoPlayer?.openRouteSelection()
self.externalVideoPlayer?.isActiveUpdated = { [weak self] isActive in
if let strongSelf = self {
if strongSelf.isAirPlayActive && !isActive {
strongSelf.externalVideoPlayer = nil
}
strongSelf.isAirPlayActive = isActive
strongSelf.updateDisplayPlaceholder()
}
}
}
}
@objc func openStickersButtonPressed() { @objc func openStickersButtonPressed() {
if let content = self.item?.content as? NativeVideoContent { guard let content = self.item?.content as? NativeVideoContent else {
let context = self.context return
let media = content.fileReference.abstract
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let topController = (self.baseNavigationController()?.topViewController as? ViewController)
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
topController?.present(controller, in: .window(.root), with: nil)
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
self.isInteractingPromise.set(true)
let signal = self.context.engine.stickers.stickerPacksAttachedToMedia(media: media)
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
let _ = (signal
|> deliverOnMainQueue).start(next: { [weak self] packs in
guard let strongSelf = self, !packs.isEmpty else {
return
}
let baseNavigationController = strongSelf.baseNavigationController()
baseNavigationController?.view.endEditing(true)
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { info, items, action in
let animateInAsReplacement = false
switch action {
case .add:
topController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in
return true
}), in: .window(.root))
case let .remove(positionInList):
topController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in
if case .undo = action {
let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start()
}
return true
}), in: .window(.root))
}
}, dismissed: { [weak self] in
self?.isInteractingPromise.set(false)
})
(baseNavigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root), with: nil)
})
} }
let context = self.context
let media = content.fileReference.abstract
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let topController = (self.baseNavigationController()?.topViewController as? ViewController)
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
topController?.present(controller, in: .window(.root), with: nil)
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
self.isInteractingPromise.set(true)
let signal = self.context.engine.stickers.stickerPacksAttachedToMedia(media: media)
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
let _ = (signal
|> deliverOnMainQueue).start(next: { [weak self] packs in
guard let strongSelf = self, !packs.isEmpty else {
return
}
let baseNavigationController = strongSelf.baseNavigationController()
baseNavigationController?.view.endEditing(true)
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { info, items, action in
let animateInAsReplacement = false
switch action {
case .add:
topController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in
return true
}), in: .window(.root))
case let .remove(positionInList):
topController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in
if case .undo = action {
let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start()
}
return true
}), in: .window(.root))
}
}, dismissed: { [weak self] in
self?.isInteractingPromise.set(false)
})
(baseNavigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root), with: nil)
})
} }
override func adjustForPreviewing() { override func adjustForPreviewing() {

View File

@ -799,11 +799,11 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
let duration: Int32 let duration: Int32
switch product.id { switch product.id {
case "org.telegram.telegramPremium.twelveMonths": case "org.telegram.telegramPremium.twelveMonths":
duration = 86400 * 365 duration = 12
case "org.telegram.telegramPremium.sixMonths": case "org.telegram.telegramPremium.sixMonths":
duration = 86400 * 180 duration = 6
case "org.telegram.telegramPremium.threeMonths": case "org.telegram.telegramPremium.threeMonths":
duration = 86400 * 90 duration = 3
default: default:
duration = 0 duration = 0
} }
@ -1259,13 +1259,16 @@ public final class PremiumGiftScreen: ViewControllerComponentContainer {
} }
completionImpl = { [weak self] duration in completionImpl = { [weak self] duration in
if let strongSelf = self { if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
let navigationController = strongSelf.navigationController // let introController = PremiumIntroScreen(context: context, source: .gift(from: context.account.peerId, to: peerId, duration: duration))
strongSelf.dismiss() var controllers = navigationController.viewControllers
let introController = PremiumIntroScreen(context: context, source: .gift(from: context.account.peerId, to: peerId, duration: duration)) controllers = controllers.filter { !($0 is PeerInfoScreen) && !($0 is PremiumGiftScreen) }
navigationController?.pushViewController(introController, animated: true) navigationController.setViewControllers(controllers, animated: true)
Queue.mainQueue().after(0.1, { Queue.mainQueue().after(0.1, {
introController.view.addSubview(ConfettiView(frame: introController.view.bounds)) if let topController = navigationController.viewControllers.first {
topController.view.addSubview(ConfettiView(frame: topController.view.bounds))
}
}) })
} }
} }

View File

@ -1487,21 +1487,21 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string
} else if case let .gift(fromPeerId, _, duration) = context.component.source { } else if case let .gift(fromPeerId, _, duration) = context.component.source {
if fromPeerId == context.component.context.account.peerId { if fromPeerId == context.component.context.account.peerId {
if duration >= 86400 * 365 { if duration == 12 {
secondaryTitleText = environment.strings.Premium_GiftedTitleYou_12Month(otherPeerName).string secondaryTitleText = environment.strings.Premium_GiftedTitleYou_12Month(otherPeerName).string
} else if duration >= 86400 * 180 { } else if duration == 6 {
secondaryTitleText = environment.strings.Premium_GiftedTitleYou_6Month(otherPeerName).string secondaryTitleText = environment.strings.Premium_GiftedTitleYou_6Month(otherPeerName).string
} else if duration >= 86400 * 90 { } else if duration == 3 {
secondaryTitleText = environment.strings.Premium_GiftedTitleYou_3Month(otherPeerName).string secondaryTitleText = environment.strings.Premium_GiftedTitleYou_3Month(otherPeerName).string
} else { } else {
secondaryTitleText = "" secondaryTitleText = ""
} }
} else { } else {
if duration >= 86400 * 365 { if duration == 12 {
secondaryTitleText = environment.strings.Premium_GiftedTitle_12Month(otherPeerName).string secondaryTitleText = environment.strings.Premium_GiftedTitle_12Month(otherPeerName).string
} else if duration >= 86400 * 180 { } else if duration == 6 {
secondaryTitleText = environment.strings.Premium_GiftedTitle_6Month(otherPeerName).string secondaryTitleText = environment.strings.Premium_GiftedTitle_6Month(otherPeerName).string
} else if duration >= 86400 * 90 { } else if duration == 3 {
secondaryTitleText = environment.strings.Premium_GiftedTitle_3Month(otherPeerName).string secondaryTitleText = environment.strings.Premium_GiftedTitle_3Month(otherPeerName).string
} else { } else {
secondaryTitleText = "" secondaryTitleText = ""

View File

@ -1190,8 +1190,21 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
view.expandFromPictureInPicture() view.expandFromPictureInPicture()
} }
if let validLayout = self.validLayout {
self.view.clipsToBounds = true
self.view.layer.cornerRadius = validLayout.deviceMetrics.screenCornerRadius
if #available(iOS 13.0, *) {
self.view.layer.cornerCurve = .continuous
}
self.view.layer.animatePosition(from: CGPoint(x: 353.0, y: 117.0), to: self.view.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in
self?.view.layer.cornerRadius = 0.0
})
self.view.layer.animateScale(from: 0.001, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
}
self.view.layer.allowsGroupOpacity = true self.view.layer.allowsGroupOpacity = true
self.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] _ in self.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1226,6 +1239,18 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
strongSelf.view.layer.allowsGroupOpacity = false strongSelf.view.layer.allowsGroupOpacity = false
strongSelf.dismissImpl(completion: completion) strongSelf.dismissImpl(completion: completion)
}) })
if let validLayout = self.validLayout {
self.view.clipsToBounds = true
self.view.layer.cornerRadius = validLayout.deviceMetrics.screenCornerRadius
if #available(iOS 13.0, *) {
self.view.layer.cornerCurve = .continuous
}
self.view.layer.animatePosition(from: self.view.center, to: CGPoint(x: 353.0, y: 117.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
})
self.view.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
}
} }
private func dismissImpl(completion: (() -> Void)? = nil) { private func dismissImpl(completion: (() -> Void)? = nil) {

View File

@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "gift.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,188 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 2.335022 2.334961 cm
0.000000 0.000000 0.000000 scn
3.865000 17.330078 m
3.837526 17.330078 l
3.300828 17.330086 2.857980 17.330093 2.497252 17.300621 c
2.122623 17.270012 1.778399 17.204330 1.455116 17.039610 c
0.953664 16.784107 0.545970 16.376415 0.290468 15.874963 c
0.125747 15.551680 0.060066 15.207455 0.029457 14.832827 c
-0.000016 14.472098 -0.000009 14.029249 0.000000 13.492552 c
0.000000 13.465077 l
0.000000 7.865078 l
0.000000 7.837605 l
-0.000009 7.300907 -0.000016 6.858058 0.029457 6.497330 c
0.060066 6.122702 0.125747 5.778477 0.290468 5.455194 c
0.545970 4.953741 0.953664 4.546048 1.455116 4.290545 c
1.778399 4.125825 2.122623 4.060143 2.497252 4.029535 c
2.857977 4.000063 3.300821 4.000070 3.837511 4.000078 c
3.837541 4.000078 l
3.865000 4.000078 l
4.665000 4.000078 l
5.032270 4.000078 5.330000 4.297809 5.330000 4.665078 c
5.330000 5.032348 5.032270 5.330078 4.665000 5.330078 c
3.865000 5.330078 l
3.293975 5.330078 2.905699 5.330595 2.605556 5.355118 c
2.313176 5.379006 2.163463 5.422318 2.058923 5.475584 c
1.807727 5.603576 1.603498 5.807804 1.475507 6.059001 c
1.422241 6.163542 1.378929 6.313254 1.355040 6.605635 c
1.330518 6.905777 1.330000 7.294052 1.330000 7.865078 c
1.330000 13.465077 l
1.330000 14.036103 1.330518 14.424378 1.355040 14.724522 c
1.378929 15.016902 1.422241 15.166615 1.475507 15.271155 c
1.603498 15.522351 1.807727 15.726581 2.058923 15.854571 c
2.163463 15.907838 2.313176 15.951150 2.605556 15.975038 c
2.905699 15.999560 3.293974 16.000078 3.865000 16.000078 c
15.465000 16.000078 l
16.036026 16.000078 16.424301 15.999560 16.724445 15.975038 c
17.016823 15.951150 17.166538 15.907838 17.271076 15.854571 c
17.522274 15.726581 17.726501 15.522351 17.854492 15.271155 c
17.907761 15.166615 17.951073 15.016902 17.974960 14.724522 c
17.999481 14.424378 18.000000 14.036104 18.000000 13.465078 c
18.000000 7.865078 l
18.000000 7.294053 17.999481 6.905778 17.974960 6.605635 c
17.951073 6.313254 17.907761 6.163542 17.854492 6.059001 c
17.726501 5.807804 17.522274 5.603576 17.271076 5.475584 c
17.166538 5.422318 17.016823 5.379006 16.724445 5.355118 c
16.424301 5.330595 16.036026 5.330078 15.465000 5.330078 c
14.665000 5.330078 l
14.297730 5.330078 14.000000 5.032348 14.000000 4.665078 c
14.000000 4.297809 14.297730 4.000078 14.665000 4.000078 c
15.465000 4.000078 l
15.492459 4.000078 l
15.492488 4.000078 l
16.029179 4.000070 16.472023 4.000063 16.832748 4.029535 c
17.207378 4.060143 17.551601 4.125825 17.874886 4.290545 c
18.376335 4.546048 18.784031 4.953741 19.039532 5.455194 c
19.204254 5.778477 19.269936 6.122702 19.300545 6.497330 c
19.330015 6.858045 19.330009 7.300875 19.330002 7.837547 c
19.330002 7.837619 l
19.330002 7.865078 l
19.330002 13.465078 l
19.330002 13.492537 l
19.330002 13.492610 l
19.330009 14.029282 19.330015 14.472111 19.300545 14.832827 c
19.269936 15.207455 19.204254 15.551680 19.039532 15.874963 c
18.784031 16.376415 18.376335 16.784107 17.874886 17.039610 c
17.551601 17.204330 17.207378 17.270012 16.832748 17.300621 c
16.472019 17.330093 16.029171 17.330086 15.492474 17.330078 c
15.465000 17.330078 l
3.865000 17.330078 l
h
9.549997 5.417668 m
9.624264 5.444814 9.705738 5.444814 9.780005 5.417668 c
9.779352 5.417907 9.779119 5.418036 9.779340 5.417960 c
9.779449 5.417922 9.779672 5.417834 9.780010 5.417685 c
9.785703 5.415166 9.824282 5.395164 9.915651 5.303084 c
10.027624 5.190243 10.164604 5.026791 10.383287 4.764371 c
11.967221 2.863649 l
12.324518 2.434894 12.562548 2.148232 12.716402 1.925475 c
12.838650 1.748481 12.861221 1.674737 12.864748 1.663215 c
12.864949 1.662560 l
12.864219 1.564913 12.820898 1.472416 12.746351 1.409344 c
12.745722 1.409081 l
12.734627 1.404418 12.663531 1.374542 12.449274 1.355145 c
12.179653 1.330734 11.807049 1.330078 11.248934 1.330078 c
8.081068 1.330078 l
7.522953 1.330078 7.150349 1.330734 6.880728 1.355145 c
6.666473 1.374543 6.595378 1.404416 6.584280 1.409080 c
6.583652 1.409343 l
6.509105 1.472416 6.465783 1.564913 6.465053 1.662560 c
6.465254 1.663213 l
6.468780 1.674733 6.491349 1.748475 6.613600 1.925474 c
6.767454 2.148230 7.005483 2.434893 7.362779 2.863647 c
8.946714 4.764370 l
9.165398 5.026790 9.302377 5.190243 9.414351 5.303084 c
9.505718 5.395163 9.544298 5.415166 9.549992 5.417685 c
9.551012 5.418137 9.550975 5.418026 9.549997 5.417668 c
h
10.236588 6.666842 m
9.867472 6.801756 9.462530 6.801756 9.093414 6.666842 c
8.834289 6.572128 8.636753 6.407670 8.470269 6.239893 c
8.312561 6.080961 8.138463 5.872022 7.942029 5.636275 c
7.924980 5.615815 l
6.341045 3.715093 l
6.320368 3.690281 l
5.989172 3.292866 5.711553 2.959741 5.519254 2.681324 c
5.330824 2.408508 5.141049 2.075929 5.135232 1.690181 c
5.127517 1.178505 5.355523 0.691704 5.753542 0.370064 c
6.053606 0.127583 6.430592 0.060459 6.760806 0.030563 c
7.097775 0.000055 7.531381 0.000065 8.048665 0.000076 c
8.048759 0.000076 l
8.081068 0.000076 l
11.248934 0.000076 l
11.281242 0.000076 l
11.281337 0.000076 l
11.798622 0.000065 12.232226 0.000055 12.569197 0.030563 c
12.899410 0.060459 13.276397 0.127583 13.576460 0.370064 c
13.974480 0.691704 14.202486 1.178505 14.194770 1.690181 c
14.188953 2.075929 13.999178 2.408508 13.810747 2.681325 c
13.618445 2.959745 13.340822 3.292877 13.009618 3.690300 c
12.988955 3.715096 l
11.405022 5.615816 l
11.387974 5.636274 l
11.191540 5.872021 11.017441 6.080961 10.859733 6.239894 c
10.693249 6.407670 10.495712 6.572128 10.236588 6.666842 c
h
f*
n
Q
endstream
endobj
3 0 obj
5505
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000005595 00000 n
0000005618 00000 n
0000005791 00000 n
0000005865 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
5924
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Airplay.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -14,6 +14,8 @@ import UrlEscaping
import TelegramStringFormatting import TelegramStringFormatting
import WallpaperBackgroundNode import WallpaperBackgroundNode
import ReactionSelectionNode import ReactionSelectionNode
import AnimatedStickerNode
import TelegramAnimatedStickerNode
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId) -> NSAttributedString? { private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: false) return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: false)
@ -29,8 +31,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private let mediaBackgroundNode: NavigationBackgroundNode private let mediaBackgroundNode: NavigationBackgroundNode
private let titleNode: TextNode private let titleNode: TextNode
private let subtitleNode: TextNode private let subtitleNode: TextNode
private let animationNode: AnimatedStickerNode
private let giftNode: ASImageNode
private let buttonNode: HighlightTrackingButtonNode private let buttonNode: HighlightTrackingButtonNode
private let buttonStarsNode: PremiumStarsNode private let buttonStarsNode: PremiumStarsNode
@ -62,10 +63,10 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.buttonNode = HighlightTrackingButtonNode() self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true self.buttonNode.clipsToBounds = true
self.buttonNode.cornerRadius = 17.0 self.buttonNode.cornerRadius = 17.0
self.giftNode = ASImageNode() self.animationNode = DefaultAnimatedStickerNodeImpl()
self.giftNode.isUserInteractionEnabled = false self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Gift"), width: 384, height: 384, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.giftNode.displaysAsynchronously = false self.animationNode.visibility = true
self.buttonStarsNode = PremiumStarsNode() self.buttonStarsNode = PremiumStarsNode()
@ -80,7 +81,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.addSubnode(self.mediaBackgroundNode) self.addSubnode(self.mediaBackgroundNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode) self.addSubnode(self.subtitleNode)
self.addSubnode(self.giftNode) self.addSubnode(self.animationNode)
self.addSubnode(self.buttonNode) self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.buttonStarsNode) self.buttonNode.addSubnode(self.buttonStarsNode)
@ -128,7 +129,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
let imageSize = CGSize(width: 220.0, height: 210.0) let giftSize = CGSize(width: 220.0, height: 240.0)
let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: item.message, accountPeerId: item.context.account.peerId) let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: item.message, accountPeerId: item.context.account.peerId)
@ -139,7 +140,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
switch action.action { switch action.action {
case let .giftPremium(_, _, durationValue): case let .giftPremium(_, _, durationValue):
duration = item.presentationData.strings.Notification_PremiumGift_Subtitle(timeIntervalString(strings: item.presentationData.strings, value: durationValue)).string duration = item.presentationData.strings.Notification_PremiumGift_Subtitle(item.presentationData.strings.Notification_PremiumGift_Months(durationValue)).string
default: default:
break break
} }
@ -148,11 +149,11 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_Title, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: imageSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_Title, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: duration, font: Font.regular(13.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: imageSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: duration, font: Font.regular(13.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: imageSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
var labelRects = labelLayout.linesRects() var labelRects = labelLayout.linesRects()
if labelRects.count > 1 { if labelRects.count > 1 {
@ -184,7 +185,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
backgroundMaskUpdated = true backgroundMaskUpdated = true
} }
let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + imageSize.height + 18.0) let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + giftSize.height + 18.0)
return (backgroundSize.width, { boundingWidth in return (backgroundSize.width, { boundingWidth in
return (backgroundSize, { [weak self] animation, synchronousLoads, _ in return (backgroundSize, { [weak self] animation, synchronousLoads, _ in
@ -193,7 +194,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.backgroundColorNode.backgroundColor = selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper) strongSelf.backgroundColorNode.backgroundColor = selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 16.0), size: imageSize) let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: labelLayout.size.height + 16.0), size: giftSize)
let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0) let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0)
strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame
@ -201,10 +202,9 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.mediaBackgroundNode.update(size: mediaBackgroundFrame.size, transition: .immediate) strongSelf.mediaBackgroundNode.update(size: mediaBackgroundFrame.size, transition: .immediate)
strongSelf.buttonNode.backgroundColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) strongSelf.buttonNode.backgroundColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12)
strongSelf.giftNode.image = UIImage(bundleImageName: "Components/Gift") let iconSize = CGSize(width: 160.0, height: 160.0)
if let image = strongSelf.giftNode.image { strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0), size: iconSize)
strongSelf.giftNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - image.size.width) / 2.0), y: mediaBackgroundFrame.minY + 14.0), size: image.size) strongSelf.animationNode.updateLayout(size: iconSize)
}
let _ = labelApply() let _ = labelApply()
let _ = titleApply() let _ = titleApply()
@ -214,7 +214,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: 2.0), size: labelLayout.size) let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: 2.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame strongSelf.labelNode.frame = labelFrame
let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 121.0), size: titleLayout.size) let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame strongSelf.titleNode.frame = titleFrame
let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY - 1.0), size: subtitleLayout.size) let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY - 1.0), size: subtitleLayout.size)

View File

@ -0,0 +1,121 @@
import Foundation
import AVFoundation
import SwiftSignalKit
import UniversalMediaPlayer
import AccountContext
import AVKit
public class ExternalVideoPlayer: NSObject, AVRoutePickerViewDelegate {
private let context: AccountContext
let content: NativeVideoContent
let player: AVPlayer?
private var didPlayToEndTimeObserver: NSObjectProtocol?
private var timeObserver: Any?
private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .buffering(initial: true, whilePlaying: false, progress: 0.0, display: true), soundEnabled: true)
private let _status = ValuePromise<MediaPlayerStatus>()
var status: Signal<MediaPlayerStatus, NoError> {
return self._status.get()
}
private var seekId: Int = 0
private weak var routePickerView: UIView?
public var isActiveUpdated: (Bool) -> Void = { _ in }
public init(context: AccountContext, content: NativeVideoContent) {
self.context = context
self.content = content
if let path = context.account.postbox.mediaBox.completedResourcePath(content.fileReference.media.resource, pathExtension: "mp4") {
let player = AVPlayer(url: URL(fileURLWithPath: path))
self.player = player
} else {
self.player = nil
}
super.init()
self.startObservingForAirPlayStatusChanges()
self.isActiveUpdated(self.player?.isExternalPlaybackActive ?? false)
if let player = self.player {
self.didPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil, using: { [weak self] notification in
if let strongSelf = self {
strongSelf.player?.seek(to: CMTime(seconds: 0.0, preferredTimescale: 30))
strongSelf.play()
}
})
self.timeObserver = player.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 10), queue: DispatchQueue.main) { [weak self] time in
guard let strongSelf = self else {
return
}
strongSelf.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: strongSelf.statusValue.duration, dimensions: CGSize(), timestamp: CMTimeGetSeconds(time), baseRate: 1.0, seekId: strongSelf.seekId, status: strongSelf.statusValue.status, soundEnabled: true)
strongSelf._status.set(strongSelf.statusValue)
}
}
self._status.set(self.statusValue)
}
deinit {
if let timeObserver = self.timeObserver {
self.player?.removeTimeObserver(timeObserver)
}
if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver {
NotificationCenter.default.removeObserver(didPlayToEndTimeObserver)
}
self.stopObservingForAirPlayStatusChanges()
}
public func play() {
self.player?.play()
}
public func openRouteSelection() {
if #available(iOS 11.0, *) {
let routePickerView = AVRoutePickerView()
routePickerView.delegate = self
if #available(iOS 13.0, *) {
routePickerView.prioritizesVideoDevices = true
}
self.context.sharedContext.mainWindow?.viewController?.view.addSubview(routePickerView)
if let routePickerButton = routePickerView.subviews.first(where: { $0 is UIButton }) as? UIButton {
routePickerButton.sendActions(for: .touchUpInside)
}
}
}
@available(iOS 11.0, *)
public func routePickerViewDidEndPresentingRoutes(_ routePickerView: AVRoutePickerView) {
routePickerView.removeFromSuperview()
self.play()
}
private var observerContextAirplay = 1
func startObservingForAirPlayStatusChanges()
{
self.player?.addObserver(self, forKeyPath: #keyPath(AVPlayer.isExternalPlaybackActive), options: .new, context: &observerContextAirplay)
}
func stopObservingForAirPlayStatusChanges()
{
self.player?.removeObserver(self, forKeyPath: #keyPath(AVPlayer.isExternalPlaybackActive))
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &observerContextAirplay {
self.isActiveUpdated(self.player?.isExternalPlaybackActive ?? false)
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}

View File

@ -777,6 +777,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
self.controller?.present(alertController, in: .window(.root)) self.controller?.present(alertController, in: .window(.root))
} }
case "web_app_setup_closing_behavior":
if let json = json, let _ = json["need_confirmation"] as? String {
}
default: default:
break break
} }