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