AVPlayer seekToTime方法无法在正确的位置播放

37

我有一个AVPlayer,正在播放HLS视频流。我的用户界面提供了一行按钮,每个按钮对应视频中的一个“章节”(按钮标记为“1”,“2”,“3”等)。应用程序从服务器下载包含以秒为单位标识的章节分割点列表的元数据。例如,一个视频长达12分钟 - 章节分割点列表为0、58、71、230、530等等。

当用户点击其中一个“章节按钮”时,按钮处理器代码会执行以下操作:

            [self.avPlayer pause];

    [self.avPlayer seekToTime: CMTimeMakeWithSeconds(seekTime, 600) 
              toleranceBefore: kCMTimeZero 
               toleranceAfter: kCMTimeZero 
            completionHandler: ^(BOOL finished) 
            {
                [self.avPlayer play];
            }];

“seekTime”是一个本地变量,其中包含了上面所述的切入点。

问题在于视频并不总是从正确的位置开始。有时候它会。但有时它会在请求的“seekTime”之前0.1秒到2秒的任意位置。它永远不会在请求的“seekTime”之后开始。

这里是关于视频编码的一些统计信息:

编码器: handbrakeCLI 编解码器: h.264 帧率: 24 (实际上是23.976 - 和拍摄方式相同) 视频比特率: 多个比特率 (64/150/300/500/800/1200) 音频比特率: 128k 关键帧: 23.976 (每秒1个)

当然,我正在使用苹果的mediafilesegmenter工具和variantplaylistcreator生成播放列表。

这些文件是从Amazon Cloud/S3存储桶中提供的。

我仍然不清楚的一个领域是CMTimeMakeWithSeconds - 我已经尝试了几种不同的方法基于我读过的不同文章/文档。例如,在上面的摘录中,我使用:

CMTimeMakeWithSeconds(seekTime, 600)

我还尝试过:

CMTimeMakeWithSeconds(seekTime, 1)

我无法确定哪个是正确的,尽管这两种方法似乎都产生了相同的不一致结果!

我还尝试过:

CMTimeMakeWithSeconds(seekTime, 23.967)

一些文章声称这就像一个分子/分母,所以n/1应该是正确的,其中“n”是秒数(如在CMTimeMakeWithseconds(n, 1)中)。但是,这段代码最初是由另一个程序员创建的(他现在已经离开了),他使用了600的preferredTimeScale(即CMTimeMakeWithseconds(n, 600))。

有人可以提供任何线索,表明我做错了什么,或者我正在尝试实现的那种准确性甚至是可能的吗?

如果有人想提供“替代”解决方案,我们已经考虑将视频分成单独的流,每章一个,但我们认为这样做不会给我们带来相同的性能,因为更改章节将需要更长的时间,因为必须创建和加载新的AVPlayerItem等等。所以如果你认为这是唯一可行的解决方案(我们确实希望这将实现我们想要的结果 - 即每个章节将从我们想要的地方精确开始),请随便说。

提前感谢!

5个回答

95
int32_t timeScale = self.player.currentItem.asset.duration.timescale;
CMTime time = CMTimeMakeWithSeconds(77.000000, timeScale);
[self.player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];

我在使用 "seekToTime" 遇到了问题,但我通过以下代码解决了我的问题。这个问题的关键是 "timescale"。

Swift 版本:

let playerTimescale = self.player.currentItem?.asset.duration.timescale ?? 1
let time =  CMTime(seconds: 77.000000, preferredTimescale: playerTimescale)
self.player.seek(to: time, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) { (finished) in /* Add your completion code here */
}

7
@jordan-bigel应该被选择为正确的答案。 - Jason Wiener
不错的解决方案 @Muhmd - Jagveer Singh
1
谢谢,你也救了我。 - YoonHo
2
这篇文章解释了为什么这个解决方案可行。重点是它不能只使用 seekToTime:,还必须实现零容忍。 - danielhadar
1
self.player.currentItem?.duration.timescale 可以工作吗?还是需要访问 asset - Oleksii Nezhyborets
谢谢,节省了我的时间。CMTime(秒:00.000000),并且它从视频的第一秒开始。 - Erhan Demirci

3
我的建议: 1)不要使用[avplayer seekToTime: toleranceBefore: toleranceAfter:],这会延迟您的搜索时间4-5秒。
2)将HLS视频切割为每段10秒。您的章节开始位置应该适合值,这是10的倍数。由于该段以I帧开头,因此您可以快速搜索并准确计时。

8
如果不使用[AVPlayer seekToTime: toleranceBefore: toleranceAfter:]方法,那么应该使用什么方法? - Fennelouski
1
我不同意这个观点。问题是“AVPlayer seekToTime在不正确的位置播放”。通过将toleranceBefore:toleranceAfter:参数添加到设置为kCMTimeZero的seekToTime()中,您将解决此问题,因此这是问题的正确答案。如果您不使用此功能,则播放器开始的位置是任意的。 - Bocaxica
谢谢你指出这个问题,我用另一种方法解决了延迟的问题。 - Salman Khakwani
我在使用Bento4 MP4HLS时加入了--segment-duration=10选项,似乎已经解决了这个问题,感谢。 - Vince Lowe

1
将此代码放入,可能会解决您的问题。
let targetTime = CMTimeMakeWithSeconds(videoLastDuration, 1) // videoLastDuration hold the previous video state.
self.playerController.player?.currentItem?.seekToTime(targetTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)

1
请使用以下函数: [player seekToTime:CMTimeMakeWithSeconds(seekTime,1)]。由于您的公差值 kCMTimeZero 会花费更多时间来寻找。可以使用kCMTimeIndefinite代替使用公差值kCMTimeZero,它与我之前指定的函数相当。

对不起,但这个回答如何解决问题? - Johannes Fahrenkrug
1
错误。时间刻度为1意味着您只能指定要搜索的整秒数。时间刻度是每秒的部分数。对于视频,请使用600,因为这是苹果建议的常见视频帧速率(如50、60、25和24帧每秒)的乘积。 - Simon Tillson

1

Swift5

let seconds = 45.0
let time = CMTimeMake(value: seconds, timescale: 1)
player?.seek(to: time, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接