17 #include <unordered_set> 32 NSMutableDictionary<NSValue *, NSSet<CALayer *> *> *
_cues;
51 if ((
self = [super init])) {
57 - (void)renderLoop:(CADisplayLink *)sender {
58 [
self->_target renderLoop];
67 - (instancetype)initWithFrame:(CGRect)frame {
68 if ((
self = [super initWithFrame:frame])) {
74 - (instancetype)initWithCoder:(NSCoder *)coder {
75 if ((
self = [super initWithCoder:coder])) {
82 if ((
self = [super init])) {
84 [
self setPlayer:player];
90 [_renderDisplayLink invalidate];
91 [_textLoopTimer invalidate];
96 [CADisplayLink displayLinkWithTarget:[[
LoopWrapper alloc] initWithTarget:self]
97 selector:@selector(renderLoop:)];
98 [_renderDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
103 _textLoopTimer = [NSTimer timerWithTimeInterval:0.25
105 block:^(NSTimer *timer) {
108 [[NSRunLoop mainRunLoop] addTimer:_textLoopTimer forMode:NSRunLoopCommonModes];
112 _imageLayer = [CALayer layer];
113 [
self.layer addSublayer:_imageLayer];
116 _textLayer = [CALayer layer];
117 [
self.layer addSublayer:_textLayer];
120 _imageLayer.actions = @{
@"position": [NSNull null],
@"bounds": [NSNull null]};
123 _cues = [[NSMutableDictionary alloc] init];
131 if (_avPlayerLayer) {
132 [_avPlayerLayer removeFromSuperlayer];
133 _avPlayerLayer = nil;
141 [
self.layer addSublayer:_avPlayerLayer];
145 - (void)setVideoGravity:(AVLayerVideoGravity)videoGravity {
146 if (videoGravity == AVLayerVideoGravityResize) {
148 }
else if (videoGravity == AVLayerVideoGravityResizeAspectFill) {
150 }
else if (videoGravity == AVLayerVideoGravityResizeAspect) {
153 [NSException raise:NSGenericException format:@"Invalid value for videoGravity"];
156 _player.mediaPlayer->SetVideoFillMode(_gravity);
163 self->_imageLayer.contents = nil;
168 if (CGImageRef image = _player.videoRenderer->Render(
nullptr, &aspect_ratio)) {
169 _imageLayer.contents = (__bridge_transfer id)image;
173 CGImageGetHeight(image)};
177 static_cast<uint32_t
>(
self.bounds.size.width),
178 static_cast<uint32_t>(
self.bounds.size.height),
183 _player.videoRenderer->fill_mode(), &src, &
dest);
184 _imageLayer.contentsRect = CGRectMake(
185 static_cast<CGFloat>(src.
x) / image_bounds.
w, static_cast<CGFloat>(src.
y) / image_bounds.
h,
186 static_cast<CGFloat>(src.
w) / image_bounds.
w, static_cast<CGFloat>(src.
h) / image_bounds.
h);
187 _imageLayer.frame = CGRectMake(dest.
x, dest.
y, dest.
w, dest.
h);
191 - (void)layoutSubviews {
192 [
super layoutSubviews];
194 _avPlayerLayer.frame =
self.bounds;
198 BOOL sizeChanged = _textLayer.frame.size.width != _imageLayer.frame.size.width ||
199 _textLayer.frame.size.height != _imageLayer.frame.size.height;
201 _textLayer.frame = _imageLayer.frame;
202 _textLayer.hidden = ![
self remakeTextCues:sizeChanged];
205 - (BOOL)remakeTextCues:(BOOL)sizeChanged {
209 auto text_tracks = _player.mediaPlayer->TextTracks();
210 if (text_tracks.empty())
212 auto activeCues = text_tracks[0]->active_cues(_player.currentTime);
218 for (CALayer *layer in [_textLayer.sublayers copy])
219 [layer removeFromSuperlayer];
220 [_cues removeAllObjects];
225 UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
228 for (
unsigned long i = 0; i < activeCues.size(); i++) {
230 std::shared_ptr<shaka::media::VTTCue> cue = activeCues[activeCues.size() - i - 1];
231 if (_cues[[NSValue valueWithPointer:cue.get()]] != nil)
234 NSString *cueText = [NSString stringWithUTF8String:cue->text().c_str()];
235 NSMutableSet<CALayer *> *layers = [[NSMutableSet alloc] init];
236 for (NSString *line in [cueText componentsSeparatedByString:
@"\n"]) {
237 CATextLayer *cueLayer = [[CATextLayer alloc] init];
238 cueLayer.string = line;
239 cueLayer.font = CGFontCreateWithFontName(static_cast<CFStringRef>(font.fontName));
240 cueLayer.fontSize = font.pointSize;
241 cueLayer.backgroundColor = [UIColor blackColor].CGColor;
242 cueLayer.foregroundColor = [UIColor whiteColor].CGColor;
243 cueLayer.alignmentMode = kCAAlignmentCenter;
248 CGFloat
width =
static_cast<CGFloat
>(cue->size() * (_textLayer.bounds.size.width) * 0.01);
249 NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin;
251 CGSize effectiveSize = [line boundingRectWithSize:CGSizeMake(width, 9999)
253 attributes:@{NSFontAttributeName: font}
257 width = ceil(effectiveSize.width) + 16;
258 CGFloat
height = ceil(effectiveSize.height);
259 CGFloat x = (_textLayer.bounds.size.width -
width) * 0.5f;
260 cueLayer.frame = CGRectMake(x, 0, width, height);
262 [_textLayer addSublayer:cueLayer];
263 [layers addObject:cueLayer];
265 [_cues setObject:layers forKey:[NSValue valueWithPointer:cue.get()]];
269 std::unordered_set<shaka::media::VTTCue *> activeCuesSet;
270 for (
auto &cue : activeCues)
271 activeCuesSet.insert(cue.get());
272 NSMutableSet<CALayer *> *expectedLayers = [[NSMutableSet alloc] init];
273 for (NSValue *cue in [_cues allKeys]) {
274 if (activeCuesSet.count(reinterpret_cast<shaka::media::VTTCue *>([cue pointerValue])) == 0) {
276 [_cues removeObjectForKey:cue];
278 NSSet<CALayer *> *layers = _cues[cue];
279 [expectedLayers unionSet:layers];
284 for (CALayer *layer in [_textLayer.sublayers copy]) {
285 if (![expectedLayers containsObject:layer])
286 [layer removeFromSuperlayer];
290 CGFloat y = _textLayer.bounds.size.height;
291 for (
auto i = _textLayer.sublayers.count; i > 0; i--) {
293 CALayer *cueLayer = _textLayer.sublayers[i - 1];
295 CGSize size = cueLayer.frame.size;
297 cueLayer.frame = CGRectMake(cueLayer.frame.origin.x, y, size.width, size.height);
shaka::media::DefaultMediaPlayer * mediaPlayer
NSMutableDictionary< NSValue *, NSSet< CALayer * > * > * _cues
shaka::VideoFillMode _gravity
CADisplayLink * _renderDisplayLink
void FitVideoToRegion(ShakaRect< uint32_t > frame, ShakaRect< uint32_t > bounds, Rational< uint32_t > sample_aspect_ratio, VideoFillMode mode, ShakaRect< uint32_t > *src, ShakaRect< uint32_t > *dest)