如何为 AVPlayerController 的字幕添加手势?

How do I add gesture to AVPlayerController's subtitles?

请帮我解决问题..我找不到任何关于这个的问题..

我要在 AVPlayerViewController 中添加字幕。

我做到了。但是,我的客户想在字幕中添加手势。

例如,

  1. 点击字幕的某个关键词
  2. 将“点击的关键字”传递给另一个控制器
  3. 只需显示另一个带有点击关键字的控制器。

首先...是否可以在字幕中添加手势?

我没有很多编码经验...

我的英语不是很好..sry..TT

안녕吴成金!欢迎来到论坛。不幸的是,简短的回答是 。没有 api 可以在特定点击位置查询单词,甚至不会使用 UILabel(或 CATextLayer)呈现内容。

较长的答案是:如果您真的想这样做,有多种选择,但我不会将其用于生产。在显示字幕时检查 AVPlayerViewController 的视图层次结构会告诉您字幕是在 FigFCRCALayerOutputNodeLayer 中呈现的。在模拟器上 运行 iOS 14.4 这一层位于 avPlayerVc.view.subviews.first?.subviews.first?.subviews.first?.layer.sublayers?.first?.sublayers?[1].sublayers?.first?.sublayers?.first?.sublayers?.first(这可能会在未来的任何时候改变,甚至可能在这个版本以下都不起作用)。文字直接设置为图层content(包括圆角半透明背景视图)

我玩了一会儿 Vision,虽然它有点滞后,但它可以将图层内容 (CGImage) 提供给文本识别请求,然后将结果拆分为单词并检查触摸位置是否在范围内词的界限。为了获得触摸点,我将 AVPlayerViewController 子类化(我知道这很糟糕,但如果直接使用 AVPlayerLayer 会更容易)并将触摸从 touchesBegan 转换为屏幕坐标:

final class PlayerVC: AVPlayerViewController {
  var onTap: ((CGPoint) -> Void)?

  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)

    guard let tapLocation = touches.first?.location(in: nil) else { return }
    onTap?(tapLocation)
  }
}

在该位置获取单词的实际代码如下所示(playerVc 指向一个 PlayerVC 实例:

func tap(tapLocation: CGPoint) {
    guard
        let subtitleLayer = playerVc.view.subviews.first?.subviews.first?.subviews.first?.layer.sublayers?.first?.sublayers?[1].sublayers?.first?.sublayers?.first?.sublayers?.first,
        CFGetTypeID(subtitleLayer.contents as CFTypeRef) == CGImage.typeID
    else { return }

    let image = subtitleLayer.contents as! CGImage
    let requestHandler = VNImageRequestHandler(cgImage: image)
    let recognizeTextHandler: (VNRequest, Error?) -> Void = { request, error in
        guard let observations = request.results as? [VNRecognizedTextObservation] else { return }

        for observation in observations {
            guard let topCandidate = observation.topCandidates(1).first, topCandidate.string != "" else { continue }

            for word in topCandidate.string.components(separatedBy: " ") {
                guard let range = topCandidate.string.range(of: word) else { continue }

                if let boundinBox = try? topCandidate.boundingBox(for: range) {

                    let transform = CGAffineTransform.identity
                        .scaledBy(x: 1, y: -1)
                        .translatedBy(x: 0, y: -subtitleLayer.frame.size.height)
                        .scaledBy(x: subtitleLayer.frame.size.width, y: subtitleLayer.frame.size.height)

                    let convertedTopLeft = boundinBox.topLeft.applying(transform)
                    let convertedBottomRight = boundinBox.bottomRight.applying(transform)

                    let localRect = CGRect(x: convertedTopLeft.x,
                                                                 y: convertedTopLeft.y,
                                                                 width: convertedBottomRight.x - convertedTopLeft.x,
                                                                 height: convertedBottomRight.y - convertedTopLeft.y)

                    let globalRect = subtitleLayer.convert(localRect, to: nil)

                    if globalRect.contains(tapLocation) {
                        print("You tapped \(word)")
                    }
                }
            }
        }
    }

    let request = VNRecognizeTextRequest(completionHandler: recognizeTextHandler)
    request.usesLanguageCorrection = false
    request.recognitionLevel = .accurate

    do {
        try requestHandler.perform([request])
    } catch {
        print("Unable to perform the request \(error)")
    }
}