-
Notifications
You must be signed in to change notification settings - Fork 1k
/
LocationLayer.swift
155 lines (126 loc) · 5.66 KB
/
LocationLayer.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//
// LocationLayer.swift
// Aerial
//
// Created by Guillaume Louel on 11/12/2019.
// Copyright © 2019 Guillaume Louel. All rights reserved.
//
import Foundation
import AVKit
class LocationLayer: AnimationTextLayer {
var config: PrefsInfo.Location?
var timeObserver: Any?
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Our inits
override init(withLayer: CALayer, isPreview: Bool, offsets: LayerOffsets, manager: LayerManager) {
super.init(withLayer: withLayer, isPreview: isPreview, offsets: offsets, manager: manager)
}
convenience init(withLayer: CALayer, isPreview: Bool, offsets: LayerOffsets, manager: LayerManager, config: PrefsInfo.Location) {
self.init(withLayer: withLayer, isPreview: isPreview, offsets: offsets, manager: manager)
self.config = config
// Set our layer's font & corner now
(self.font, self.fontSize) = getFont(name: config.fontName,
size: config.fontSize)
self.corner = config.corner
}
// We need to clear our callbacks on the player
override func clear(player: AVPlayer) {
if timeObserver != nil {
player.removeTimeObserver(timeObserver!)
timeObserver = nil
}
}
// Called at each new video
override func setupForVideo(video: AerialVideo, player: AVPlayer) {
let poiStringProvider = PoiStringProvider.sharedInstance
// We need to make sure we actually have descriptions to show.
// Custom videos, and earlier tvOS videos may not
if poiStringProvider.hasPoiKeys(video: video) {
// Grab a sorted array of timestamps and the keys
let (keys, times) = getKeysAndTimestamps(video: video)
// Animate the very first one on it's own
var initialKey = keys["0"]!
// Oh Apple... This is a temporary fix for Coit Tower Night where a key was reused
if initialKey == "A004_C012_0" && video.id == "b6-4" {
initialKey = "A004_C012_100"
}
let str = poiStringProvider.getString(key: initialKey, video: video)
let duration = calculateAnimationDuration(times: times, current: times[0], video: video)
let fadeAnimation = createFadeInOutAnimation(duration: duration)
update(string: str)
add(fadeAnimation, forKey: "textfade")
// AVPlayer requires NSValues of CMTime
var timevals = [NSValue]()
for time in times {
timevals.append(NSValue(time: time))
}
// We then callback for each timestamp
timeObserver = player.addBoundaryTimeObserver(forTimes: timevals, queue: DispatchQueue.main) {
// find closest timestamp to when we're waking up
var closest = 1000.0
var closestTime = CMTime.zero
for time in times {
let ts = time.seconds
let distance = abs(ts - player.currentTime().seconds)
if distance < closest {
closest = distance
closestTime = time
}
}
// Get the string for the current timestamp
let key = String(format: "%.0f", closestTime.seconds)
let str = poiStringProvider.getString(key: keys[key]!, video: video)
let duration = self.calculateAnimationDuration(times: times, current: closestTime, video: video)
let fadeAnimation = self.createFadeInOutAnimation(duration: duration)
self.update(string: str)
self.add(fadeAnimation, forKey: "textfade")
}
} else {
// We don't have any extended description, using Secondary name (location) or video name (City)
let str: String
if video.secondaryName != "" {
str = video.secondaryName
} else {
str = video.name
}
let duration = self.calculateAnimationDuration(times: [], current: CMTime.zero, video: video)
let fadeAnimation = self.createFadeInOutAnimation(duration: duration)
update(string: str)
add(fadeAnimation, forKey: "textfade")
}
}
// MARK: - Time helpers
func getKeysAndTimestamps(video: AerialVideo) -> ([String: String], [CMTime]) {
let poiStringProvider = PoiStringProvider.sharedInstance
// Collect all the timestamps and keys from the JSON
// They are store as [Time, Key]
let keys = poiStringProvider.getPoiKeys(video: video)
var times = [CMTime]()
for pkv in keys {
let timeStamp = Double(pkv.key)!
times.append(CMTime(seconds: timeStamp, preferredTimescale: 1))
}
// The JSON isn't sorted though, so we fix that
times.sort(by: { $0.seconds < $1.seconds })
return (keys, times)
}
func calculateAnimationDuration(times: [CMTime], current: CMTime, video: AerialVideo) -> Double {
// We may only show for 10s
if PrefsInfo.location.time == .tenSeconds {
return 10
} else {
if let idx = times.firstIndex(of: current) {
if times.count > idx + 1 {
return times[idx+1].seconds - times[idx].seconds - 1
}
}
// We may not have a video duration, if so show it for 15 mins
return video.duration > 0 ? video.duration : 900
}
}
}