1 /* 2 * Copyright (C) 2013 Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "modules/mediasource/SourceBuffer.h" 33 34 #include "bindings/v8/ExceptionState.h" 35 #include "core/dom/Event.h" 36 #include "core/dom/ExceptionCode.h" 37 #include "core/dom/GenericEventQueue.h" 38 #include "core/html/TimeRanges.h" 39 #include "core/platform/Logging.h" 40 #include "core/platform/graphics/SourceBufferPrivate.h" 41 #include "modules/mediasource/MediaSource.h" 42 #include "wtf/ArrayBuffer.h" 43 #include "wtf/ArrayBufferView.h" 44 #include "wtf/MathExtras.h" 45 46 #include <limits> 47 48 namespace WebCore { 49 50 PassRefPtr<SourceBuffer> SourceBuffer::create(PassOwnPtr<SourceBufferPrivate> sourceBufferPrivate, MediaSource* source, GenericEventQueue* asyncEventQueue) 51 { 52 RefPtr<SourceBuffer> sourceBuffer(adoptRef(new SourceBuffer(sourceBufferPrivate, source, asyncEventQueue))); 53 sourceBuffer->suspendIfNeeded(); 54 return sourceBuffer.release(); 55 } 56 57 SourceBuffer::SourceBuffer(PassOwnPtr<SourceBufferPrivate> sourceBufferPrivate, MediaSource* source, GenericEventQueue* asyncEventQueue) 58 : ActiveDOMObject(source->scriptExecutionContext()) 59 , m_private(sourceBufferPrivate) 60 , m_source(source) 61 , m_asyncEventQueue(asyncEventQueue) 62 , m_updating(false) 63 , m_timestampOffset(0) 64 , m_appendWindowStart(0) 65 , m_appendWindowEnd(std::numeric_limits<double>::infinity()) 66 , m_appendBufferTimer(this, &SourceBuffer::appendBufferTimerFired) 67 , m_removeTimer(this, &SourceBuffer::removeTimerFired) 68 , m_pendingRemoveStart(-1) 69 , m_pendingRemoveEnd(-1) 70 { 71 ASSERT(m_private); 72 ASSERT(m_source); 73 ScriptWrappable::init(this); 74 } 75 76 SourceBuffer::~SourceBuffer() 77 { 78 ASSERT(isRemoved()); 79 } 80 81 PassRefPtr<TimeRanges> SourceBuffer::buffered(ExceptionState& es) const 82 { 83 // Section 3.1 buffered attribute steps. 84 // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an 85 // InvalidStateError exception and abort these steps. 86 if (isRemoved()) { 87 es.throwDOMException(InvalidStateError); 88 return 0; 89 } 90 91 // 2. Return a new static normalized TimeRanges object for the media segments buffered. 92 return m_private->buffered(); 93 } 94 95 double SourceBuffer::timestampOffset() const 96 { 97 return m_timestampOffset; 98 } 99 100 void SourceBuffer::setTimestampOffset(double offset, ExceptionState& es) 101 { 102 // Section 3.1 timestampOffset attribute setter steps. 103 // 1. Let new timestamp offset equal the new value being assigned to this attribute. 104 // 2. If this object has been removed from the sourceBuffers attribute of the parent media source, then throw an 105 // InvalidStateError exception and abort these steps. 106 // 3. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. 107 if (isRemoved() || m_updating) { 108 es.throwDOMException(InvalidStateError); 109 return; 110 } 111 112 // 4. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: 113 // 4.1 Set the readyState attribute of the parent media source to "open" 114 // 4.2 Queue a task to fire a simple event named sourceopen at the parent media source. 115 m_source->openIfInEndedState(); 116 117 // 5. If this object is waiting for the end of a media segment to be appended, then throw an InvalidStateError 118 // and abort these steps. 119 // 120 // FIXME: Add step 6 text when mode attribute is implemented. 121 if (!m_private->setTimestampOffset(offset)) { 122 es.throwDOMException(InvalidStateError); 123 return; 124 } 125 126 // 7. Update the attribute to new timestamp offset. 127 m_timestampOffset = offset; 128 } 129 130 double SourceBuffer::appendWindowStart() const 131 { 132 return m_appendWindowStart; 133 } 134 135 void SourceBuffer::setAppendWindowStart(double start, ExceptionState& es) 136 { 137 // Enforce throwing an exception on restricted double values. 138 if (std::isnan(start) 139 || start == std::numeric_limits<double>::infinity() 140 || start == -std::numeric_limits<double>::infinity()) { 141 es.throwDOMException(TypeMismatchError); 142 return; 143 } 144 145 // Section 3.1 appendWindowStart attribute setter steps. 146 // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an 147 // InvalidStateError exception and abort these steps. 148 // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. 149 if (isRemoved() || m_updating) { 150 es.throwDOMException(InvalidStateError); 151 return; 152 } 153 154 // 3. If the new value is less than 0 or greater than or equal to appendWindowEnd then throw an InvalidAccessError 155 // exception and abort these steps. 156 if (start < 0 || start >= m_appendWindowEnd) { 157 es.throwDOMException(InvalidAccessError); 158 return; 159 } 160 161 m_private->setAppendWindowStart(start); 162 163 // 4. Update the attribute to the new value. 164 m_appendWindowStart = start; 165 } 166 167 double SourceBuffer::appendWindowEnd() const 168 { 169 return m_appendWindowEnd; 170 } 171 172 void SourceBuffer::setAppendWindowEnd(double end, ExceptionState& es) 173 { 174 // Section 3.1 appendWindowEnd attribute setter steps. 175 // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an 176 // InvalidStateError exception and abort these steps. 177 // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. 178 if (isRemoved() || m_updating) { 179 es.throwDOMException(InvalidStateError); 180 return; 181 } 182 183 // 3. If the new value equals NaN, then throw an InvalidAccessError and abort these steps. 184 // 4. If the new value is less than or equal to appendWindowStart then throw an InvalidAccessError 185 // exception and abort these steps. 186 if (std::isnan(end) || end <= m_appendWindowStart) { 187 es.throwDOMException(InvalidAccessError); 188 return; 189 } 190 191 m_private->setAppendWindowEnd(end); 192 193 // 5. Update the attribute to the new value. 194 m_appendWindowEnd = end; 195 } 196 197 void SourceBuffer::appendBuffer(PassRefPtr<ArrayBuffer> data, ExceptionState& es) 198 { 199 // Section 3.2 appendBuffer() 200 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data 201 // 1. If data is null then throw an InvalidAccessError exception and abort these steps. 202 if (!data) { 203 es.throwDOMException(InvalidAccessError); 204 return; 205 } 206 207 appendBufferInternal(static_cast<unsigned char*>(data->data()), data->byteLength(), es); 208 } 209 210 void SourceBuffer::appendBuffer(PassRefPtr<ArrayBufferView> data, ExceptionState& es) 211 { 212 // Section 3.2 appendBuffer() 213 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data 214 // 1. If data is null then throw an InvalidAccessError exception and abort these steps. 215 if (!data) { 216 es.throwDOMException(InvalidAccessError); 217 return; 218 } 219 220 appendBufferInternal(static_cast<unsigned char*>(data->baseAddress()), data->byteLength(), es); 221 } 222 223 void SourceBuffer::abort(ExceptionState& es) 224 { 225 // Section 3.2 abort() method steps. 226 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-abort-void 227 // 1. If this object has been removed from the sourceBuffers attribute of the parent media source 228 // then throw an InvalidStateError exception and abort these steps. 229 // 2. If the readyState attribute of the parent media source is not in the "open" state 230 // then throw an InvalidStateError exception and abort these steps. 231 if (isRemoved() || !m_source->isOpen()) { 232 es.throwDOMException(InvalidStateError); 233 return; 234 } 235 236 // 3. If the sourceBuffer.updating attribute equals true, then run the following steps: ... 237 abortIfUpdating(); 238 239 // 4. Run the reset parser state algorithm. 240 m_private->abort(); 241 242 // 5. Set appendWindowStart to 0. 243 setAppendWindowStart(0, es); 244 245 // 6. Set appendWindowEnd to positive Infinity. 246 setAppendWindowEnd(std::numeric_limits<double>::infinity(), es); 247 } 248 249 void SourceBuffer::remove(double start, double end, ExceptionState& es) 250 { 251 // Section 3.2 remove() method steps. 252 // 1. If start is negative or greater than duration, then throw an InvalidAccessError exception and abort these steps. 253 // 2. If end is less than or equal to start, then throw an InvalidAccessError exception and abort these steps. 254 if (start < 0 || (m_source && (std::isnan(m_source->duration()) || start > m_source->duration())) || end <= start) { 255 es.throwDOMException(InvalidAccessError); 256 return; 257 } 258 259 // 3. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an 260 // InvalidStateError exception and abort these steps. 261 // 4. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. 262 if (isRemoved() || m_updating) { 263 es.throwDOMException(InvalidStateError); 264 return; 265 } 266 267 // 5. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: 268 // 5.1. Set the readyState attribute of the parent media source to "open" 269 // 5.2. Queue a task to fire a simple event named sourceopen at the parent media source . 270 m_source->openIfInEndedState(); 271 272 // 6. Set the updating attribute to true. 273 m_updating = true; 274 275 // 7. Queue a task to fire a simple event named updatestart at this SourceBuffer object. 276 scheduleEvent(eventNames().updatestartEvent); 277 278 // 8. Return control to the caller and run the rest of the steps asynchronously. 279 m_pendingRemoveStart = start; 280 m_pendingRemoveEnd = end; 281 m_removeTimer.startOneShot(0); 282 } 283 284 void SourceBuffer::abortIfUpdating() 285 { 286 // Section 3.2 abort() method step 3 substeps. 287 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-abort-void 288 289 if (!m_updating) 290 return; 291 292 // 3.1. Abort the buffer append and stream append loop algorithms if they are running. 293 m_appendBufferTimer.stop(); 294 m_pendingAppendData.clear(); 295 296 m_removeTimer.stop(); 297 m_pendingRemoveStart = -1; 298 m_pendingRemoveEnd = -1; 299 300 // 3.2. Set the updating attribute to false. 301 m_updating = false; 302 303 // 3.3. Queue a task to fire a simple event named abort at this SourceBuffer object. 304 scheduleEvent(eventNames().abortEvent); 305 306 // 3.4. Queue a task to fire a simple event named updateend at this SourceBuffer object. 307 scheduleEvent(eventNames().updateendEvent); 308 } 309 310 void SourceBuffer::removedFromMediaSource() 311 { 312 if (isRemoved()) 313 return; 314 315 abortIfUpdating(); 316 317 m_private->removedFromMediaSource(); 318 m_source = 0; 319 m_asyncEventQueue = 0; 320 } 321 322 bool SourceBuffer::hasPendingActivity() const 323 { 324 return m_source; 325 } 326 327 void SourceBuffer::stop() 328 { 329 m_appendBufferTimer.stop(); 330 m_removeTimer.stop(); 331 } 332 333 ScriptExecutionContext* SourceBuffer::scriptExecutionContext() const 334 { 335 return ActiveDOMObject::scriptExecutionContext(); 336 } 337 338 const AtomicString& SourceBuffer::interfaceName() const 339 { 340 return eventNames().interfaceForSourceBuffer; 341 } 342 343 EventTargetData* SourceBuffer::eventTargetData() 344 { 345 return &m_eventTargetData; 346 } 347 348 EventTargetData* SourceBuffer::ensureEventTargetData() 349 { 350 return &m_eventTargetData; 351 } 352 353 bool SourceBuffer::isRemoved() const 354 { 355 return !m_source; 356 } 357 358 void SourceBuffer::scheduleEvent(const AtomicString& eventName) 359 { 360 ASSERT(m_asyncEventQueue); 361 362 RefPtr<Event> event = Event::create(eventName, false, false); 363 event->setTarget(this); 364 365 m_asyncEventQueue->enqueueEvent(event.release()); 366 } 367 368 void SourceBuffer::appendBufferInternal(unsigned char* data, unsigned size, ExceptionState& es) 369 { 370 // Section 3.2 appendBuffer() 371 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data 372 373 // Step 1 is enforced by the caller. 374 // 2. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an InvalidStateError exception and abort these steps. 375 // 3. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. 376 if (isRemoved() || m_updating) { 377 es.throwDOMException(InvalidStateError); 378 return; 379 } 380 381 // 4. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: ... 382 m_source->openIfInEndedState(); 383 384 // Steps 5-6 385 386 // 7. Add data to the end of the input buffer. 387 m_pendingAppendData.append(data, size); 388 389 // 8. Set the updating attribute to true. 390 m_updating = true; 391 392 // 9. Queue a task to fire a simple event named updatestart at this SourceBuffer object. 393 scheduleEvent(eventNames().updatestartEvent); 394 395 // 10. Asynchronously run the buffer append algorithm. 396 m_appendBufferTimer.startOneShot(0); 397 } 398 399 void SourceBuffer::appendBufferTimerFired(Timer<SourceBuffer>*) 400 { 401 ASSERT(m_updating); 402 403 // Section 3.5.4 Buffer Append Algorithm 404 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-buffer-append 405 406 // 1. Run the segment parser loop algorithm. 407 // Step 2 doesn't apply since we run Step 1 synchronously here. 408 size_t appendSize = m_pendingAppendData.size(); 409 if (!appendSize) { 410 // Resize buffer for 0 byte appends so we always have a valid pointer. 411 // We need to convey all appends, even 0 byte ones to |m_private| so 412 // that it can clear its end of stream state if necessary. 413 m_pendingAppendData.resize(1); 414 } 415 m_private->append(m_pendingAppendData.data(), appendSize); 416 417 // 3. Set the updating attribute to false. 418 m_updating = false; 419 m_pendingAppendData.clear(); 420 421 // 4. Queue a task to fire a simple event named update at this SourceBuffer object. 422 scheduleEvent(eventNames().updateEvent); 423 424 // 5. Queue a task to fire a simple event named updateend at this SourceBuffer object. 425 scheduleEvent(eventNames().updateendEvent); 426 } 427 428 void SourceBuffer::removeTimerFired(Timer<SourceBuffer>*) 429 { 430 ASSERT(m_updating); 431 ASSERT(m_pendingRemoveStart >= 0); 432 ASSERT(m_pendingRemoveStart < m_pendingRemoveEnd); 433 434 // Section 3.2 remove() method steps 435 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-remove-void-double-start-double-end 436 437 // 9. Run the coded frame removal algorithm with start and end as the start and end of the removal range. 438 m_private->remove(m_pendingRemoveStart, m_pendingRemoveEnd); 439 440 // 10. Set the updating attribute to false. 441 m_updating = false; 442 m_pendingRemoveStart = -1; 443 m_pendingRemoveEnd = -1; 444 445 // 11. Queue a task to fire a simple event named update at this SourceBuffer object. 446 scheduleEvent(eventNames().updateEvent); 447 448 // 12. Queue a task to fire a simple event named updateend at this SourceBuffer object. 449 scheduleEvent(eventNames().updateendEvent); 450 } 451 452 } // namespace WebCore 453