Lets assume I have a CMTimeRange
constructed from start time
zero, and
duration
of 40 seconds.
I want to split this CMTimeRange
into multiple chunks by a X seconds divider. So the total duration
of the chunks will be the same duration
as the original duration, and each startTime
will reflect the endTime
of of the previous chunk. The last chunk will be the modulus of the left over seconds.
For example, for video of 40 seconds, and divider of 15 seconds per chunk:
- First
CMTimeRange
- start time: 0, duration: 15 seconds.
- Second
CMTimeRange
- start time: 15, duration: 15 seconds.
- Third
CMTimeRange
- start time: 30, duration: 10 seconds. (left overs)
What I've tried:
I tried using CMTimeSubtract
on the total duration and use the result again, in recursive way untill the CMTime in invalid, But it doesn't seems to work.
Any help will be highly appreciated.
Best Regards, Roi
Starting at range.start
, create ranges of the given length
until range.end
is reached:
func splitIntoChunks(range: CMTimeRange, length: CMTime) -> [CMTimeRange] {
var chunks: [CMTimeRange] = []
var from = range.start
while from < range.end {
chunks.append(CMTimeRange(start: from, duration: length).intersection(range))
from = from + length
}
return chunks
}
intersection
is used here to prune the last chunk to the original range.
Alternative solution:
func splitIntoChunks(range: CMTimeRange, length: CMTime) -> [CMTimeRange] {
return stride(from: range.start.seconds, to: range.end.seconds, by: length.seconds).map {
CMTimeRange(start: CMTime(seconds: $0, preferredTimescale: length.timescale), duration: length)
.intersection(range)
}
}
With a custom extension to make CMTime
adopt the Strideable
protocol
extension CMTime: Strideable {
public func distance(to other: CMTime) -> TimeInterval {
return other - self
}
public func advanced(by n: TimeInterval) -> CMTime {
return self + n
}
}
this can be further simplified to
func splitIntoChunks(range: CMTimeRange, length: CMTime) -> [CMTimeRange] {
return stride(from: range.start, to: range.end, by: length.seconds).map {
CMTimeRange(start: $0, duration: length) .intersection(range)
}
}
In any case, you'll might want to add a check
precondition(length.seconds > 0, "length must be positive")
to your function, in order to detect invalid calls during development.
I too needed to stride CMTime
, to deal with AVCaptureDevice
exposure durations & showing these to users.
Turns out Martin's answer doesn't work anymore with the changes in Swift 4.x / XCode 10. Here's my version of CMTime
conformance to Strideable
:
extension CMTime: Strideable {
public func distance(to other: CMTime) -> TimeInterval {
return TimeInterval((Double(other.value) / Double(other.timescale)) - (Double(self.value) / Double(self.timescale)))
}
public func advanced(by n: TimeInterval) -> CMTime {
var retval = self
retval.value += CMTimeValue(n * TimeInterval(self.timescale))
return retval
}
}
I derped with it in a playground and it seems to work.