1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include <QtCore/qdebug.h> |
5 | |
6 | #include "qmediatimerange.h" |
7 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | /*! |
11 | \class QMediaTimeRange::Interval |
12 | \brief The QMediaTimeRange::Interval class represents a time interval with |
13 | integer precision. |
14 | \inmodule QtMultimedia |
15 | |
16 | \ingroup multimedia |
17 | \ingroup multimedia_core |
18 | |
19 | An interval is specified by an inclusive start() and end() time. These |
20 | must be set in the constructor, as this is an immutable class. The |
21 | specific units of time represented by the class have not been defined - it |
22 | is suitable for any times which can be represented by a signed 64 bit |
23 | integer. |
24 | |
25 | The isNormal() method determines if a time interval is normal (a normal |
26 | time interval has start() <= end()). A normal interval can be received |
27 | from an abnormal interval by calling the normalized() method. |
28 | |
29 | The contains() method determines if a specified time lies within the time |
30 | interval. |
31 | |
32 | The translated() method returns a time interval which has been translated |
33 | forwards or backwards through time by a specified offset. |
34 | |
35 | \sa QMediaTimeRange |
36 | */ |
37 | |
38 | /*! |
39 | \fn QMediaTimeRange::Interval::Interval(qint64 start, qint64 end) |
40 | |
41 | Constructs an interval with the specified \a start and \a end times. |
42 | */ |
43 | |
44 | /*! |
45 | \fn QMediaTimeRange::Interval::start() const |
46 | |
47 | Returns the start time of the interval. |
48 | |
49 | \sa end() |
50 | */ |
51 | |
52 | /*! |
53 | \fn QMediaTimeRange::Interval::end() const |
54 | |
55 | Returns the end time of the interval. |
56 | |
57 | \sa start() |
58 | */ |
59 | |
60 | /*! |
61 | \fn QMediaTimeRange::Interval::contains(qint64 time) const |
62 | |
63 | Returns true if the time interval contains the specified \a time. |
64 | That is, start() <= time <= end(). |
65 | */ |
66 | |
67 | /*! |
68 | \fn QMediaTimeRange::Interval::isNormal() const |
69 | |
70 | Returns true if this time interval is normal. |
71 | A normal time interval has start() <= end(). |
72 | |
73 | \sa normalized() |
74 | */ |
75 | |
76 | /*! |
77 | \fn QMediaTimeRange::Interval::normalized() const |
78 | |
79 | Returns a normalized version of this interval. |
80 | |
81 | If the start() time of the interval is greater than the end() time, |
82 | then the returned interval has the start and end times swapped. |
83 | */ |
84 | |
85 | /*! |
86 | \fn QMediaTimeRange::Interval::translated(qint64 offset) const |
87 | |
88 | Returns a copy of this time interval, translated by a value of \a offset. |
89 | An interval can be moved forward through time with a positive offset, or backward |
90 | through time with a negative offset. |
91 | */ |
92 | |
93 | /*! |
94 | \fn bool QMediaTimeRange::Interval::operator==(QMediaTimeRange::Interval lhs, QMediaTimeRange::Interval rhs) |
95 | |
96 | Returns true if \a lhs is exactly equal to \a rhs. |
97 | */ |
98 | |
99 | /*! |
100 | \fn bool QMediaTimeRange::Interval::operator!=(QMediaTimeRange::Interval lhs, QMediaTimeRange::Interval rhs) |
101 | |
102 | Returns true if \a lhs is not exactly equal to \a rhs. |
103 | */ |
104 | |
105 | class QMediaTimeRangePrivate : public QSharedData |
106 | { |
107 | public: |
108 | QMediaTimeRangePrivate() = default; |
109 | QMediaTimeRangePrivate(const QMediaTimeRange::Interval &interval); |
110 | |
111 | QList<QMediaTimeRange::Interval> intervals; |
112 | |
113 | void addInterval(const QMediaTimeRange::Interval &interval); |
114 | void removeInterval(const QMediaTimeRange::Interval &interval); |
115 | }; |
116 | |
117 | QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QMediaTimeRangePrivate); |
118 | |
119 | QMediaTimeRangePrivate::QMediaTimeRangePrivate(const QMediaTimeRange::Interval &interval) |
120 | { |
121 | if (interval.isNormal()) |
122 | intervals << interval; |
123 | } |
124 | |
125 | void QMediaTimeRangePrivate::addInterval(const QMediaTimeRange::Interval &interval) |
126 | { |
127 | // Handle normalized intervals only |
128 | if (!interval.isNormal()) |
129 | return; |
130 | |
131 | // Find a place to insert the interval |
132 | int i; |
133 | for (i = 0; i < intervals.size(); i++) { |
134 | // Insert before this element |
135 | if(interval.s < intervals[i].s) { |
136 | intervals.insert(i, t: interval); |
137 | break; |
138 | } |
139 | } |
140 | |
141 | // Interval needs to be added to the end of the list |
142 | if (i == intervals.size()) |
143 | intervals.append(t: interval); |
144 | |
145 | // Do we need to correct the element before us? |
146 | if (i > 0 && intervals[i - 1].e >= interval.s - 1) |
147 | i--; |
148 | |
149 | // Merge trailing ranges |
150 | while (i < intervals.size() - 1 |
151 | && intervals[i].e >= intervals[i + 1].s - 1) { |
152 | intervals[i].e = qMax(a: intervals[i].e, b: intervals[i + 1].e); |
153 | intervals.removeAt(i: i + 1); |
154 | } |
155 | } |
156 | |
157 | void QMediaTimeRangePrivate::removeInterval(const QMediaTimeRange::Interval &interval) |
158 | { |
159 | // Handle normalized intervals only |
160 | if (!interval.isNormal()) |
161 | return; |
162 | |
163 | for (int i = 0; i < intervals.size(); i++) { |
164 | const QMediaTimeRange::Interval r = intervals.at(i); |
165 | |
166 | if (r.e < interval.s) { |
167 | // Before the removal interval |
168 | continue; |
169 | } else if (interval.e < r.s) { |
170 | // After the removal interval - stop here |
171 | break; |
172 | } else if (r.s < interval.s && interval.e < r.e) { |
173 | // Split case - a single range has a chunk removed |
174 | intervals[i].e = interval.s -1; |
175 | addInterval(interval: QMediaTimeRange::Interval(interval.e + 1, r.e)); |
176 | break; |
177 | } else if (r.s < interval.s) { |
178 | // Trimming Tail Case |
179 | intervals[i].e = interval.s - 1; |
180 | } else if (interval.e < r.e) { |
181 | // Trimming Head Case - we can stop after this |
182 | intervals[i].s = interval.e + 1; |
183 | break; |
184 | } else { |
185 | // Complete coverage case |
186 | intervals.removeAt(i); |
187 | --i; |
188 | } |
189 | } |
190 | } |
191 | |
192 | /*! |
193 | \class QMediaTimeRange |
194 | \brief The QMediaTimeRange class represents a set of zero or more disjoint |
195 | time intervals. |
196 | \ingroup multimedia |
197 | \inmodule QtMultimedia |
198 | |
199 | \reentrant |
200 | |
201 | The earliestTime(), latestTime(), intervals() and isEmpty() |
202 | methods are used to get information about the current time range. |
203 | |
204 | The addInterval(), removeInterval() and clear() methods are used to modify |
205 | the current time range. |
206 | |
207 | When adding or removing intervals from the time range, existing intervals |
208 | within the range may be expanded, trimmed, deleted, merged or split to ensure |
209 | that all intervals within the time range remain distinct and disjoint. As a |
210 | consequence, all intervals added or removed from a time range must be |
211 | \l{QMediaTimeRange::Interval::isNormal()}{normal}. |
212 | |
213 | \sa QMediaTimeRange::Interval |
214 | */ |
215 | |
216 | /*! |
217 | Constructs an empty time range. |
218 | */ |
219 | QMediaTimeRange::QMediaTimeRange() |
220 | : d(new QMediaTimeRangePrivate) |
221 | { |
222 | |
223 | } |
224 | |
225 | /*! |
226 | Constructs a time range that contains an initial interval from |
227 | \a start to \a end inclusive. |
228 | |
229 | If the interval is not \l{QMediaTimeRange::Interval::isNormal()}{normal}, |
230 | the resulting time range will be empty. |
231 | |
232 | \sa addInterval() |
233 | */ |
234 | QMediaTimeRange::QMediaTimeRange(qint64 start, qint64 end) |
235 | : QMediaTimeRange(Interval(start, end)) |
236 | { |
237 | } |
238 | |
239 | /*! |
240 | Constructs a time range that contains an initial interval, \a interval. |
241 | |
242 | If \a interval is not \l{QMediaTimeRange::Interval::isNormal()}{normal}, |
243 | the resulting time range will be empty. |
244 | |
245 | \sa addInterval() |
246 | */ |
247 | QMediaTimeRange::QMediaTimeRange(const QMediaTimeRange::Interval &interval) |
248 | : d(new QMediaTimeRangePrivate(interval)) |
249 | { |
250 | } |
251 | |
252 | /*! |
253 | Constructs a time range by copying another time \a range. |
254 | */ |
255 | QMediaTimeRange::QMediaTimeRange(const QMediaTimeRange &range) noexcept = default; |
256 | |
257 | /*! \fn QMediaTimeRange::QMediaTimeRange(QMediaTimeRange &&other) |
258 | |
259 | Constructs a time range by moving from \a other. |
260 | */ |
261 | |
262 | /*! |
263 | \fn void QMediaTimeRange::swap(QMediaTimeRange &other) noexcept |
264 | |
265 | Swaps the current instance with the \a other. |
266 | */ |
267 | |
268 | /*! |
269 | \fn QMediaTimeRange::~QMediaTimeRange() |
270 | |
271 | Destructor. |
272 | */ |
273 | QMediaTimeRange::~QMediaTimeRange() = default; |
274 | |
275 | /*! |
276 | Takes a copy of the \a other time range and returns itself. |
277 | */ |
278 | QMediaTimeRange &QMediaTimeRange::operator=(const QMediaTimeRange &other) noexcept = default; |
279 | |
280 | /*! |
281 | \fn QMediaTimeRange &QMediaTimeRange::operator=(QMediaTimeRange &&other) |
282 | |
283 | Moves \a other into this time range. |
284 | */ |
285 | |
286 | /*! |
287 | Sets the time range to a single continuous interval, \a interval. |
288 | */ |
289 | QMediaTimeRange &QMediaTimeRange::operator=(const QMediaTimeRange::Interval &interval) |
290 | { |
291 | d = new QMediaTimeRangePrivate(interval); |
292 | return *this; |
293 | } |
294 | |
295 | /*! |
296 | Returns the earliest time within the time range. |
297 | |
298 | For empty time ranges, this value is equal to zero. |
299 | |
300 | \sa latestTime() |
301 | */ |
302 | qint64 QMediaTimeRange::earliestTime() const |
303 | { |
304 | if (!d->intervals.isEmpty()) |
305 | return d->intervals[0].start(); |
306 | |
307 | return 0; |
308 | } |
309 | |
310 | /*! |
311 | Returns the latest time within the time range. |
312 | |
313 | For empty time ranges, this value is equal to zero. |
314 | |
315 | \sa earliestTime() |
316 | */ |
317 | qint64 QMediaTimeRange::latestTime() const |
318 | { |
319 | if (!d->intervals.isEmpty()) |
320 | return d->intervals[d->intervals.size() - 1].end(); |
321 | |
322 | return 0; |
323 | } |
324 | |
325 | /*! |
326 | \overload |
327 | |
328 | Adds the interval specified by \a start and \a end |
329 | to the time range. |
330 | |
331 | \sa addInterval() |
332 | */ |
333 | void QMediaTimeRange::addInterval(qint64 start, qint64 end) |
334 | { |
335 | detach(); |
336 | d->addInterval(interval: Interval(start, end)); |
337 | } |
338 | |
339 | /*! |
340 | Adds the specified \a interval to the time range. |
341 | |
342 | Adding intervals which are not \l{QMediaTimeRange::Interval::isNormal()}{normal} |
343 | is invalid, and will be ignored. |
344 | |
345 | If the specified interval is adjacent to, or overlaps existing |
346 | intervals within the time range, these intervals will be merged. |
347 | |
348 | This operation takes linear time. |
349 | |
350 | \sa removeInterval() |
351 | */ |
352 | void QMediaTimeRange::addInterval(const QMediaTimeRange::Interval &interval) |
353 | { |
354 | detach(); |
355 | d->addInterval(interval); |
356 | } |
357 | |
358 | /*! |
359 | Adds each of the intervals in \a range to this time range. |
360 | |
361 | Equivalent to calling addInterval() for each interval in \a range. |
362 | */ |
363 | void QMediaTimeRange::addTimeRange(const QMediaTimeRange &range) |
364 | { |
365 | detach(); |
366 | const auto intervals = range.intervals(); |
367 | for (const Interval &i : intervals) { |
368 | d->addInterval(interval: i); |
369 | } |
370 | } |
371 | |
372 | /*! |
373 | \overload |
374 | |
375 | Removes the interval specified by \a start and \a end |
376 | from the time range. |
377 | |
378 | \sa removeInterval() |
379 | */ |
380 | void QMediaTimeRange::removeInterval(qint64 start, qint64 end) |
381 | { |
382 | detach(); |
383 | d->removeInterval(interval: Interval(start, end)); |
384 | } |
385 | |
386 | /*! |
387 | \fn QMediaTimeRange::removeInterval(const QMediaTimeRange::Interval &interval) |
388 | |
389 | Removes the specified \a interval from the time range. |
390 | |
391 | Removing intervals which are not \l{QMediaTimeRange::Interval::isNormal()}{normal} |
392 | is invalid, and will be ignored. |
393 | |
394 | Intervals within the time range will be trimmed, split or deleted |
395 | such that no intervals within the time range include any part of the |
396 | target interval. |
397 | |
398 | This operation takes linear time. |
399 | |
400 | \sa addInterval() |
401 | */ |
402 | void QMediaTimeRange::removeInterval(const QMediaTimeRange::Interval &interval) |
403 | { |
404 | detach(); |
405 | d->removeInterval(interval); |
406 | } |
407 | |
408 | /*! |
409 | Removes each of the intervals in \a range from this time range. |
410 | |
411 | Equivalent to calling removeInterval() for each interval in \a range. |
412 | */ |
413 | void QMediaTimeRange::removeTimeRange(const QMediaTimeRange &range) |
414 | { |
415 | detach(); |
416 | const auto intervals = range.intervals(); |
417 | for (const Interval &i : intervals) { |
418 | d->removeInterval(interval: i); |
419 | } |
420 | } |
421 | |
422 | /*! |
423 | Adds each interval in \a other to the time range and returns the result. |
424 | */ |
425 | QMediaTimeRange& QMediaTimeRange::operator+=(const QMediaTimeRange &other) |
426 | { |
427 | addTimeRange(range: other); |
428 | return *this; |
429 | } |
430 | |
431 | /*! |
432 | Adds the specified \a interval to the time range and returns the result. |
433 | */ |
434 | QMediaTimeRange& QMediaTimeRange::operator+=(const QMediaTimeRange::Interval &interval) |
435 | { |
436 | addInterval(interval); |
437 | return *this; |
438 | } |
439 | |
440 | /*! |
441 | Removes each interval in \a other from the time range and returns the result. |
442 | */ |
443 | QMediaTimeRange& QMediaTimeRange::operator-=(const QMediaTimeRange &other) |
444 | { |
445 | removeTimeRange(range: other); |
446 | return *this; |
447 | } |
448 | |
449 | /*! |
450 | Removes the specified \a interval from the time range and returns the result. |
451 | */ |
452 | QMediaTimeRange& QMediaTimeRange::operator-=(const QMediaTimeRange::Interval &interval) |
453 | { |
454 | removeInterval(interval); |
455 | return *this; |
456 | } |
457 | |
458 | /*! |
459 | \fn QMediaTimeRange::clear() |
460 | |
461 | Removes all intervals from the time range. |
462 | |
463 | \sa removeInterval() |
464 | */ |
465 | void QMediaTimeRange::clear() |
466 | { |
467 | detach(); |
468 | d->intervals.clear(); |
469 | } |
470 | |
471 | /*! |
472 | \internal |
473 | */ |
474 | void QMediaTimeRange::detach() |
475 | { |
476 | d.detach(); |
477 | } |
478 | |
479 | /*! |
480 | \fn QMediaTimeRange::intervals() const |
481 | |
482 | Returns the list of intervals covered by this time range. |
483 | */ |
484 | QList<QMediaTimeRange::Interval> QMediaTimeRange::intervals() const |
485 | { |
486 | return d->intervals; |
487 | } |
488 | |
489 | /*! |
490 | \fn QMediaTimeRange::isEmpty() const |
491 | |
492 | Returns true if there are no intervals within the time range. |
493 | |
494 | \sa intervals() |
495 | */ |
496 | bool QMediaTimeRange::isEmpty() const |
497 | { |
498 | return d->intervals.isEmpty(); |
499 | } |
500 | |
501 | /*! |
502 | \fn QMediaTimeRange::isContinuous() const |
503 | |
504 | Returns true if the time range consists of a continuous interval. |
505 | That is, there is one or fewer disjoint intervals within the time range. |
506 | */ |
507 | bool QMediaTimeRange::isContinuous() const |
508 | { |
509 | return (d->intervals.size() <= 1); |
510 | } |
511 | |
512 | /*! |
513 | \fn QMediaTimeRange::contains(qint64 time) const |
514 | |
515 | Returns true if the specified \a time lies within the time range. |
516 | */ |
517 | bool QMediaTimeRange::contains(qint64 time) const |
518 | { |
519 | for (int i = 0; i < d->intervals.size(); i++) { |
520 | if (d->intervals[i].contains(time)) |
521 | return true; |
522 | |
523 | if (time < d->intervals[i].start()) |
524 | break; |
525 | } |
526 | |
527 | return false; |
528 | } |
529 | |
530 | /*! |
531 | \fn bool QMediaTimeRange::operator==(const QMediaTimeRange &lhs, const QMediaTimeRange &rhs) |
532 | |
533 | Returns true if all intervals in \a lhs are present in \a rhs. |
534 | */ |
535 | |
536 | /*! |
537 | \fn bool QMediaTimeRange::operator!=(const QMediaTimeRange &lhs, const QMediaTimeRange &rhs) |
538 | |
539 | Returns true if one or more intervals in \a lhs are not present in \a rhs. |
540 | */ |
541 | |
542 | /*! |
543 | \fn QMediaTimeRange operator+(const QMediaTimeRange &r1, const QMediaTimeRange &r2) |
544 | \relates QMediaTimeRange |
545 | |
546 | Returns a time range containing the union between \a r1 and \a r2. |
547 | */ |
548 | |
549 | /*! |
550 | \fn QMediaTimeRange operator-(const QMediaTimeRange &r1, const QMediaTimeRange &r2) |
551 | \relates QMediaTimeRange |
552 | |
553 | Returns a time range containing \a r2 subtracted from \a r1. |
554 | */ |
555 | |
556 | #ifndef QT_NO_DEBUG_STREAM |
557 | QDebug operator<<(QDebug dbg, const QMediaTimeRange::Interval &interval) |
558 | { |
559 | QDebugStateSaver saver(dbg); |
560 | dbg.nospace(); |
561 | dbg << "QMediaTimeRange::Interval( " << interval.start() << ", " << interval.end() << " )" ; |
562 | return dbg; |
563 | } |
564 | |
565 | QDebug operator<<(QDebug dbg, const QMediaTimeRange &range) |
566 | { |
567 | QDebugStateSaver saver(dbg); |
568 | dbg.nospace(); |
569 | dbg << "QMediaTimeRange( " ; |
570 | const auto intervals = range.intervals(); |
571 | for (const auto &interval : intervals) |
572 | dbg << '(' << interval.start() << ", " << interval.end() << ") " ; |
573 | dbg.space(); |
574 | dbg << ')'; |
575 | return dbg; |
576 | } |
577 | #endif |
578 | |
579 | QT_END_NAMESPACE |
580 | |