Разделить CMTimeRange на несколько фрагментов CMTimeRange

Предположим, у меня есть CMTimeRange, построенный из start time нуля и duration из 40 секунд.

Я хочу разделить это CMTimeRange на несколько частей с разделителем X секунд. Таким образом, total duration чанков будет таким же duration, что и исходная продолжительность, и каждый startTime будет отражать endTime предыдущего чанка. Последний фрагмент будет модулем оставшихся секунд.

Например, для видео продолжительностью 40 секунд и разделителем 15 секунд на фрагмент:

  1. Первая CMTimeRange - время начала: 0, продолжительность: 15 секунд.
  2. Секунда CMTimeRange - время начала: 15, продолжительность: 15 секунд.
  3. Третий CMTimeRange - время начала: 30, продолжительность: 10 секунд. (left overs)

Что я пробовал:

Я попытался использовать CMTimeSubtract для общей продолжительности и снова использовать результат рекурсивным способом, пока CMTime не станет недействительным, но, похоже, это не работает.

Мы будем очень признательны за любую помощь.

С уважением, Рой


person Roi Mulia    schedule 14.02.2018    source источник


Ответы (2)


Начиная с range.start, создайте диапазоны заданной длины, пока не будет достигнуто range.end:

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 используется здесь, чтобы обрезать последний фрагмент до исходного диапазона.

Альтернативное решение:

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)
    }

}

С пользовательским расширением, позволяющим CMTime использовать протокол Strideable.

extension CMTime: Strideable {
    public func distance(to other: CMTime) -> TimeInterval {
        return other - self
    }

    public func advanced(by n: TimeInterval) -> CMTime {
        return self + n
    }
}

это может быть дополнительно упрощено до

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)
    }
}

В любом случае, вы можете добавить чек

precondition(length.seconds > 0, "length must be positive")

к вашей функции, чтобы обнаруживать недопустимые вызовы во время разработки.

person Martin R    schedule 14.02.2018
comment
Оба работают просто идеально! Я должен признать, что даже с одним или двумя дополнительными днями решение, которое я бы придумал, было бы гораздо менее чистым. Еще раз спасибо Мартин. Супер благодарен! - person Roi Mulia; 14.02.2018

Мне тоже нужно было шагнуть CMTime, чтобы справиться с продолжительностью воздействия AVCaptureDevice и показать это пользователям.

Оказывается, ответ Мартина больше не работает с изменениями в Swift 4.x/XCode 10. Вот моя версия соответствия CMTime 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
    }
}

Я повозился с ним на детской площадке, и, кажется, он работает.

person xaphod    schedule 24.11.2018