59a02b0fb605646530f4457d98a8fb81ef6aae3d
[WebKit-https.git] / Source / WebCore / platform / graphics / avfoundation / InbandTextTrackPrivateAVF.cpp
1 /*
2  * Copyright (C) 2012-2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27
28 #if ENABLE(VIDEO) && (USE(AVFOUNDATION) || PLATFORM(IOS))
29
30 #include "InbandTextTrackPrivateAVF.h"
31
32 #include "ISOVTTCue.h"
33 #include "InbandTextTrackPrivateClient.h"
34 #include "Logging.h"
35 #include "MediaTimeAVFoundation.h"
36 #include <runtime/ArrayBuffer.h>
37 #include <runtime/DataView.h>
38 #include <runtime/Int8Array.h>
39 #include <wtf/MediaTime.h>
40 #include <wtf/NeverDestroyed.h>
41 #include <wtf/text/CString.h>
42 #include <wtf/text/StringBuilder.h>
43 #include <wtf/text/WTFString.h>
44 #include <wtf/unicode/CharacterNames.h>
45 #include <wtf/StringPrintStream.h>
46
47 #include "CoreMediaSoftLink.h"
48
49 namespace JSC {
50 class ArrayBuffer;
51 }
52
53 namespace WebCore {
54
55 AVFInbandTrackParent::~AVFInbandTrackParent()
56 {
57 }
58
59 InbandTextTrackPrivateAVF::InbandTextTrackPrivateAVF(AVFInbandTrackParent* owner, CueFormat format)
60     : InbandTextTrackPrivate(format)
61     , m_owner(owner)
62     , m_pendingCueStatus(None)
63     , m_index(0)
64     , m_hasBeenReported(false)
65     , m_seeking(false)
66     , m_haveReportedVTTHeader(false)
67 {
68 }
69
70 InbandTextTrackPrivateAVF::~InbandTextTrackPrivateAVF()
71 {
72     disconnect();
73 }
74
75 static bool makeRGBA32FromARGBCFArray(CFArrayRef colorArray, RGBA32& color)
76 {
77     if (CFArrayGetCount(colorArray) < 4)
78         return false;
79
80     float componentArray[4];
81     for (int i = 0; i < 4; i++) {
82         CFNumberRef value = static_cast<CFNumberRef>(CFArrayGetValueAtIndex(colorArray, i));
83         if (CFGetTypeID(value) != CFNumberGetTypeID())
84             return false;
85
86         float component;
87         CFNumberGetValue(value, kCFNumberFloatType, &component);
88         componentArray[i] = component;
89     }
90
91     color = makeRGBA32FromFloats(componentArray[1], componentArray[2], componentArray[3], componentArray[0]);
92     return true;
93 }
94
95 void InbandTextTrackPrivateAVF::processCueAttributes(CFAttributedStringRef attributedString, GenericCueData& cueData)
96 {
97     // Some of the attributes we translate into per-cue WebVTT settings are are repeated on each part of an attributed string so only
98     // process the first instance of each.
99     enum AttributeFlags {
100         Line = 1 << 0,
101         Position = 1 << 1,
102         Size = 1 << 2,
103         Vertical = 1 << 3,
104         Align = 1 << 4,
105         FontName = 1 << 5
106     };
107     unsigned processed = 0;
108
109     StringBuilder content;
110     String attributedStringValue = CFAttributedStringGetString(attributedString);
111     CFIndex length = attributedStringValue.length();
112     if (!length)
113         return;
114
115     CFRange effectiveRange = CFRangeMake(0, 0);
116     while ((effectiveRange.location + effectiveRange.length) < length) {
117
118         CFDictionaryRef attributes = CFAttributedStringGetAttributes(attributedString, effectiveRange.location + effectiveRange.length, &effectiveRange);
119         if (!attributes)
120             continue;
121
122         StringBuilder tagStart;
123         CFStringRef valueString;
124         String tagEnd;
125         CFIndex attributeCount = CFDictionaryGetCount(attributes);
126         Vector<const void*> keys(attributeCount);
127         Vector<const void*> values(attributeCount);
128         CFDictionaryGetKeysAndValues(attributes, keys.data(), values.data());
129
130         for (CFIndex i = 0; i < attributeCount; ++i) {
131             CFStringRef key = static_cast<CFStringRef>(keys[i]);
132             CFTypeRef value = values[i];
133             if (CFGetTypeID(key) != CFStringGetTypeID() || !CFStringGetLength(key))
134                 continue;
135
136             if (CFStringCompare(key, kCMTextMarkupAttribute_Alignment, 0) == kCFCompareEqualTo) {
137                 valueString = static_cast<CFStringRef>(value);
138                 if (CFGetTypeID(valueString) != CFStringGetTypeID() || !CFStringGetLength(valueString))
139                     continue;
140                 if (processed & Align)
141                     continue;
142                 processed |= Align;
143
144                 if (CFStringCompare(valueString, kCMTextMarkupAlignmentType_Start, 0) == kCFCompareEqualTo)
145                     cueData.setAlign(GenericCueData::Start);
146                 else if (CFStringCompare(valueString, kCMTextMarkupAlignmentType_Middle, 0) == kCFCompareEqualTo)
147                     cueData.setAlign(GenericCueData::Middle);
148                 else if (CFStringCompare(valueString, kCMTextMarkupAlignmentType_End, 0) == kCFCompareEqualTo)
149                     cueData.setAlign(GenericCueData::End);
150                 else
151                     ASSERT_NOT_REACHED();
152
153                 continue;
154             }
155
156             if (CFStringCompare(key, kCMTextMarkupAttribute_BoldStyle, 0) == kCFCompareEqualTo) {
157                 if (static_cast<CFBooleanRef>(value) != kCFBooleanTrue)
158                     continue;
159
160                 tagStart.appendLiteral("<b>");
161                 tagEnd = "</b>" + tagEnd;
162                 continue;
163             }
164
165             if (CFStringCompare(key, kCMTextMarkupAttribute_ItalicStyle, 0) == kCFCompareEqualTo) {
166                 if (static_cast<CFBooleanRef>(value) != kCFBooleanTrue)
167                     continue;
168
169                 tagStart.appendLiteral("<i>");
170                 tagEnd = "</i>" + tagEnd;
171                 continue;
172             }
173
174             if (CFStringCompare(key, kCMTextMarkupAttribute_UnderlineStyle, 0) == kCFCompareEqualTo) {
175                 if (static_cast<CFBooleanRef>(value) != kCFBooleanTrue)
176                     continue;
177
178                 tagStart.appendLiteral("<u>");
179                 tagEnd = "</u>" + tagEnd;
180                 continue;
181             }
182
183             if (CFStringCompare(key, kCMTextMarkupAttribute_OrthogonalLinePositionPercentageRelativeToWritingDirection, 0) == kCFCompareEqualTo) {
184                 if (CFGetTypeID(value) != CFNumberGetTypeID())
185                     continue;
186                 if (processed & Line)
187                     continue;
188                 processed |= Line;
189
190                 CFNumberRef valueNumber = static_cast<CFNumberRef>(value);
191                 double line;
192                 CFNumberGetValue(valueNumber, kCFNumberFloat64Type, &line);
193                 cueData.setLine(line);
194                 continue;
195             }
196
197             if (CFStringCompare(key, kCMTextMarkupAttribute_TextPositionPercentageRelativeToWritingDirection, 0) == kCFCompareEqualTo) {
198                 if (CFGetTypeID(value) != CFNumberGetTypeID())
199                     continue;
200                 if (processed & Position)
201                     continue;
202                 processed |= Position;
203
204                 CFNumberRef valueNumber = static_cast<CFNumberRef>(value);
205                 double position;
206                 CFNumberGetValue(valueNumber, kCFNumberFloat64Type, &position);
207                 cueData.setPosition(position);
208                 continue;
209             }
210
211             if (CFStringCompare(key, kCMTextMarkupAttribute_WritingDirectionSizePercentage, 0) == kCFCompareEqualTo) {
212                 if (CFGetTypeID(value) != CFNumberGetTypeID())
213                     continue;
214                 if (processed & Size)
215                     continue;
216                 processed |= Size;
217
218                 CFNumberRef valueNumber = static_cast<CFNumberRef>(value);
219                 double size;
220                 CFNumberGetValue(valueNumber, kCFNumberFloat64Type, &size);
221                 cueData.setSize(size);
222                 continue;
223             }
224
225             if (CFStringCompare(key, kCMTextMarkupAttribute_VerticalLayout, 0) == kCFCompareEqualTo) {
226                 valueString = static_cast<CFStringRef>(value);
227                 if (CFGetTypeID(valueString) != CFStringGetTypeID() || !CFStringGetLength(valueString))
228                     continue;
229                 
230                 if (CFStringCompare(valueString, kCMTextVerticalLayout_LeftToRight, 0) == kCFCompareEqualTo)
231                     tagStart.append(leftToRightMark);
232                 else if (CFStringCompare(valueString, kCMTextVerticalLayout_RightToLeft, 0) == kCFCompareEqualTo)
233                     tagStart.append(rightToLeftMark);
234                 continue;
235             }
236
237             if (CFStringCompare(key, kCMTextMarkupAttribute_BaseFontSizePercentageRelativeToVideoHeight, 0) == kCFCompareEqualTo) {
238                 if (CFGetTypeID(value) != CFNumberGetTypeID())
239                     continue;
240                 
241                 CFNumberRef valueNumber = static_cast<CFNumberRef>(value);
242                 double baseFontSize;
243                 CFNumberGetValue(valueNumber, kCFNumberFloat64Type, &baseFontSize);
244                 cueData.setBaseFontSize(baseFontSize);
245                 continue;
246             }
247
248             if (CFStringCompare(key, kCMTextMarkupAttribute_RelativeFontSize, 0) == kCFCompareEqualTo) {
249                 if (CFGetTypeID(value) != CFNumberGetTypeID())
250                     continue;
251                 
252                 CFNumberRef valueNumber = static_cast<CFNumberRef>(value);
253                 double relativeFontSize;
254                 CFNumberGetValue(valueNumber, kCFNumberFloat64Type, &relativeFontSize);
255                 cueData.setRelativeFontSize(relativeFontSize);
256                 continue;
257             }
258
259             if (CFStringCompare(key, kCMTextMarkupAttribute_FontFamilyName, 0) == kCFCompareEqualTo) {
260                 valueString = static_cast<CFStringRef>(value);
261                 if (CFGetTypeID(valueString) != CFStringGetTypeID() || !CFStringGetLength(valueString))
262                     continue;
263                 if (processed & FontName)
264                     continue;
265                 processed |= FontName;
266                 
267                 cueData.setFontName(valueString);
268                 continue;
269             }
270
271             if (CFStringCompare(key, kCMTextMarkupAttribute_ForegroundColorARGB, 0) == kCFCompareEqualTo) {
272                 CFArrayRef arrayValue = static_cast<CFArrayRef>(value);
273                 if (CFGetTypeID(arrayValue) != CFArrayGetTypeID())
274                     continue;
275                 
276                 RGBA32 color;
277                 if (!makeRGBA32FromARGBCFArray(arrayValue, color))
278                     continue;
279                 cueData.setForegroundColor(color);
280             }
281             
282             if (CFStringCompare(key, kCMTextMarkupAttribute_BackgroundColorARGB, 0) == kCFCompareEqualTo) {
283                 CFArrayRef arrayValue = static_cast<CFArrayRef>(value);
284                 if (CFGetTypeID(arrayValue) != CFArrayGetTypeID())
285                     continue;
286                 
287                 RGBA32 color;
288                 if (!makeRGBA32FromARGBCFArray(arrayValue, color))
289                     continue;
290                 cueData.setBackgroundColor(color);
291             }
292
293             if (CFStringCompare(key, kCMTextMarkupAttribute_CharacterBackgroundColorARGB, 0) == kCFCompareEqualTo) {
294                 CFArrayRef arrayValue = static_cast<CFArrayRef>(value);
295                 if (CFGetTypeID(arrayValue) != CFArrayGetTypeID())
296                     continue;
297                 
298                 RGBA32 color;
299                 if (!makeRGBA32FromARGBCFArray(arrayValue, color))
300                     continue;
301                 cueData.setHighlightColor(color);
302             }
303         }
304
305         content.append(tagStart);
306         content.append(attributedStringValue.substring(effectiveRange.location, effectiveRange.length));
307         content.append(tagEnd);
308     }
309
310     if (content.length())
311         cueData.setContent(content.toString());
312 }
313
314 void InbandTextTrackPrivateAVF::processCue(CFArrayRef attributedStrings, CFArrayRef nativeSamples, const MediaTime& time)
315 {
316     if (!client())
317         return;
318
319     processAttributedStrings(attributedStrings, time);
320     processNativeSamples(nativeSamples, time);
321 }
322
323 void InbandTextTrackPrivateAVF::processAttributedStrings(CFArrayRef attributedStrings, const MediaTime& time)
324 {
325     LOG(Media, "InbandTextTrackPrivateAVF::processCue - %li cues at time %s\n", attributedStrings ? CFArrayGetCount(attributedStrings) : 0, toString(time).utf8().data());
326
327     Vector<RefPtr<GenericCueData>> arrivingCues;
328     if (attributedStrings) {
329         CFIndex count = CFArrayGetCount(attributedStrings);
330         for (CFIndex i = 0; i < count; i++) {
331             CFAttributedStringRef attributedString = static_cast<CFAttributedStringRef>(CFArrayGetValueAtIndex(attributedStrings, i));
332             
333             if (!attributedString || !CFAttributedStringGetLength(attributedString))
334                 continue;
335             
336             RefPtr<GenericCueData> cueData = GenericCueData::create();
337             processCueAttributes(attributedString, *cueData.get());
338             if (!cueData->content().length())
339                 continue;
340             
341             arrivingCues.append(cueData);
342             
343             cueData->setStartTime(time);
344             cueData->setEndTime(MediaTime::positiveInfiniteTime());
345             
346             // AVFoundation cue "position" is to the center of the text so adjust relative to the edge because we will use it to
347             // set CSS "left".
348             if (cueData->position() >= 0 && cueData->size() > 0)
349                 cueData->setPosition(cueData->position() - cueData->size() / 2);
350             
351             LOG(Media, "InbandTextTrackPrivateAVF::processCue(%p) - considering cue (\"%s\") for time = %s, position =  %.2f, line =  %.2f", this, cueData->content().utf8().data(), toString(cueData->startTime()).utf8().data(), cueData->position(), cueData->line());
352             
353             cueData->setStatus(GenericCueData::Partial);
354         }
355     }
356
357     if (m_pendingCueStatus != None) {
358         // Cues do not have an explicit duration, they are displayed until the next "cue" (which might be empty) is emitted.
359         m_currentCueEndTime = time;
360
361         if (m_currentCueEndTime >= m_currentCueStartTime) {
362             for (auto& cueData : m_cues) {
363                 // See if one of the newly-arrived cues is an extension of this cue.
364                 Vector<RefPtr<GenericCueData>> nonExtensionCues;
365                 for (auto& arrivingCue : arrivingCues) {
366                     if (!arrivingCue->doesExtendCueData(*cueData))
367                         nonExtensionCues.append(arrivingCue);
368                     else
369                         LOG(Media, "InbandTextTrackPrivateAVF::processCue(%p) - found an extension cue (\"%s\") for time = %.2f, end = %.2f, position =  %.2f, line =  %.2f", this, arrivingCue->content().utf8().data(), arrivingCue->startTime().toDouble(), arrivingCue->endTime().toDouble(), arrivingCue->position(), arrivingCue->line());
370                 }
371
372                 bool currentCueIsExtended = (arrivingCues.size() != nonExtensionCues.size());
373
374                 arrivingCues = nonExtensionCues;
375                 
376                 if (currentCueIsExtended)
377                     continue;
378
379                 if (m_pendingCueStatus == Valid) {
380                     cueData->setEndTime(m_currentCueEndTime);
381                     cueData->setStatus(GenericCueData::Complete);
382
383                     LOG(Media, "InbandTextTrackPrivateAVF::processCue(%p) - updating cue \"%s\": start=%.2f, end=%.2f", this, cueData->content().utf8().data(), cueData->startTime().toDouble(), m_currentCueEndTime.toDouble());
384                     client()->updateGenericCue(this, cueData.get());
385                 } else {
386                     // We have to assume that the implicit duration is invalid for cues delivered during a seek because the AVF decode pipeline may not
387                     // see every cue, so DO NOT update cue duration while seeking.
388                     LOG(Media, "InbandTextTrackPrivateAVF::processCue(%p) - ignoring cue delivered during seek: start=%s, end=%s, content=\"%s\"", this, toString(cueData->startTime()).utf8().data(), toString(m_currentCueEndTime).utf8().data(), cueData->content().utf8().data());
389                 }
390             }
391         } else
392             LOG(Media, "InbandTextTrackPrivateAVF::processCue negative length cue(s) ignored: start=%s, end=%s\n",  toString(m_currentCueStartTime).utf8().data(), toString(m_currentCueEndTime).utf8().data());
393
394         removeCompletedCues();
395     }
396
397     if (arrivingCues.isEmpty())
398         return;
399
400     m_currentCueStartTime = time;
401
402     for (auto& cueData : arrivingCues) {
403
404         m_cues.append(cueData);
405         
406         LOG(Media, "InbandTextTrackPrivateAVF::processCue(%p) - adding cue \"%s\" for time = %.2f, end = %.2f, position =  %.2f, line =  %.2f", this, cueData->content().utf8().data(), cueData->startTime().toDouble(), cueData->endTime().toDouble(), cueData->position(), cueData->line());
407
408         client()->addGenericCue(this, cueData.release());
409     }
410
411     m_pendingCueStatus = seeking() ? DeliveredDuringSeek : Valid;
412 }
413
414 void InbandTextTrackPrivateAVF::beginSeeking()
415 {
416     // Forget any partially accumulated cue data as the seek could be to a time outside of the cue's
417     // range, which will mean that the next cue delivered will result in the current cue getting the
418     // incorrect duration.
419     resetCueValues();
420     m_seeking = true;
421 }
422
423 void InbandTextTrackPrivateAVF::disconnect()
424 {
425     m_owner = 0;
426     m_index = 0;
427 }
428
429 void InbandTextTrackPrivateAVF::removeCompletedCues()
430 {
431     if (client()) {
432         long currentCue = m_cues.size() - 1;
433         for (; currentCue >= 0; --currentCue) {
434             if (m_cues[currentCue]->status() != GenericCueData::Complete)
435                 continue;
436
437             LOG(Media, "InbandTextTrackPrivateAVF::removeCompletedCues(%p) - removing cue \"%s\": start=%.2f, end=%.2f", this, m_cues[currentCue]->content().utf8().data(), m_cues[currentCue]->startTime().toDouble(), m_cues[currentCue]->endTime().toDouble());
438
439             m_cues.remove(currentCue);
440         }
441     }
442
443     if (m_cues.isEmpty())
444         m_pendingCueStatus = None;
445
446     m_currentCueStartTime = MediaTime::zeroTime();
447     m_currentCueEndTime = MediaTime::zeroTime();
448 }
449
450 void InbandTextTrackPrivateAVF::resetCueValues()
451 {
452     if (m_currentCueEndTime && m_cues.size())
453         LOG(Media, "InbandTextTrackPrivateAVF::resetCueValues flushing data for cues: start=%s\n", toString(m_currentCueStartTime).utf8().data());
454
455     if (client()) {
456         for (size_t i = 0; i < m_cues.size(); i++)
457             client()->removeGenericCue(this, m_cues[i].get());
458     }
459
460     m_cues.resize(0);
461     m_pendingCueStatus = None;
462     m_currentCueStartTime = MediaTime::zeroTime();
463     m_currentCueEndTime = MediaTime::zeroTime();
464 }
465
466 void InbandTextTrackPrivateAVF::setMode(InbandTextTrackPrivate::Mode newMode)
467 {
468     if (!m_owner)
469         return;
470
471     InbandTextTrackPrivate::Mode oldMode = mode();
472     InbandTextTrackPrivate::setMode(newMode);
473
474     if (oldMode == newMode)
475         return;
476
477     m_owner->trackModeChanged();
478 }
479
480 void InbandTextTrackPrivateAVF::processNativeSamples(CFArrayRef nativeSamples, const MediaTime& presentationTime)
481 {
482     if (!nativeSamples)
483         return;
484
485     CFIndex count = CFArrayGetCount(nativeSamples);
486     if (!count)
487         return;
488
489     LOG(Media, "InbandTextTrackPrivateAVF::processNativeSamples - %li sample buffers at time %.2f\n", count, presentationTime.toDouble());
490
491     for (CFIndex i = 0; i < count; i++) {
492
493         CMSampleBufferRef sampleBuffer = (CMSampleBufferRef)CFArrayGetValueAtIndex(nativeSamples, i);
494         if (!sampleBuffer)
495             continue;
496
497         CMSampleTimingInfo timingInfo;
498         OSStatus status = CMSampleBufferGetSampleTimingInfo(sampleBuffer, i, &timingInfo);
499         if (status) {
500             LOG(Media, "InbandTextTrackPrivateAVF::processNativeSamples(%p) - CMSampleBufferGetSampleTimingInfo returned error %x for sample %li", this, static_cast<int>(status), i);
501             continue;
502         }
503
504         CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
505         size_t bufferLength = CMBlockBufferGetDataLength(blockBuffer);
506         if (bufferLength < ISOBox::boxHeaderSize()) {
507             LOG(Media, "InbandTextTrackPrivateAVF::processNativeSamples(%p) - ERROR: CMSampleBuffer size length unexpectedly small (%zu)!!", this, bufferLength);
508             continue;
509         }
510
511         m_sampleInputBuffer.resize(m_sampleInputBuffer.size() + bufferLength);
512         CMBlockBufferCopyDataBytes(blockBuffer, 0, bufferLength, m_sampleInputBuffer.data() + m_sampleInputBuffer.size() - bufferLength);
513
514         RefPtr<ArrayBuffer> buffer = ArrayBuffer::create(m_sampleInputBuffer.data(), m_sampleInputBuffer.size());
515
516         String type = ISOBox::peekType(buffer.get());
517         size_t boxLength = ISOBox::peekLength(buffer.get());
518         if (boxLength > buffer->byteLength()) {
519             LOG(Media, "InbandTextTrackPrivateAVF::processNativeSamples(%p) - ERROR: chunk '%s' size (%zu) larger than buffer length (%u)!!", this, type.utf8().data(), boxLength, buffer->byteLength());
520             continue;
521         }
522
523         LOG(Media, "InbandTextTrackPrivateAVF::processNativeSamples(%p) - chunk type = '%s', size = %zu", this, type.utf8().data(), boxLength);
524
525         if (type == ISOWebVTTCue::boxType()) {
526             ISOWebVTTCue cueData = ISOWebVTTCue(presentationTime, toMediaTime(timingInfo.duration), buffer.get());
527             LOG(Media, "    sample presentation time = %.2f, duration = %.2f", cueData.presentationTime().toDouble(), cueData.duration().toDouble());
528             LOG(Media, "    id = \"%s\", settings = \"%s\", cue text = \"%s\"", cueData.id().utf8().data(), cueData.settings().utf8().data(), cueData.cueText().utf8().data());
529             LOG(Media, "    sourceID = \"%s\", originalStartTime = \"%s\"", cueData.sourceID().utf8().data(), cueData.originalStartTime().utf8().data());
530
531             client()->parseWebVTTCueData(this, cueData);
532         }
533
534         do {
535             if (m_haveReportedVTTHeader)
536                 break;
537
538             CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
539             if (!formatDescription)
540                 break;
541
542             CFDictionaryRef extensions = CMFormatDescriptionGetExtensions(formatDescription);
543             if (!extensions)
544                 break;
545
546             CFDictionaryRef sampleDescriptionExtensions = static_cast<CFDictionaryRef>(CFDictionaryGetValue(extensions, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms));
547             if (!sampleDescriptionExtensions)
548                 break;
549             
550             CFDataRef webvttHeaderData = static_cast<CFDataRef>(CFDictionaryGetValue(sampleDescriptionExtensions, CFSTR("vttC")));
551             if (!webvttHeaderData)
552                 break;
553
554             unsigned length = CFDataGetLength(webvttHeaderData);
555             if (!length)
556                 break;
557
558             // A WebVTT header is terminated by "One or more WebVTT line terminators" so append two line feeds to make sure the parser
559             // reccognized this string as a full header.
560             StringBuilder header;
561             header.append(reinterpret_cast<const unsigned char*>(CFDataGetBytePtr(webvttHeaderData)), length);
562             header.append("\n\n");
563
564             LOG(Media, "    vtt header = \n%s", header.toString().utf8().data());
565             client()->parseWebVTTFileHeader(this, header.toString());
566             m_haveReportedVTTHeader = true;
567         } while (0);
568
569         m_sampleInputBuffer.remove(0, boxLength);
570     }
571 }
572
573 } // namespace WebCore
574
575 #endif // ENABLE(VIDEO) && (USE(AVFOUNDATION) || PLATFORM(IOS))