SwiftUI实现YouTube Shorts预加载:解决TikTok克隆视频切换卡顿
YouTube Shorts预加载优化方案(TikTok克隆应用)
问题描述
我正在用YouTube Shorts开发一款TikTok克隆应用,目前所有功能运行正常,但切换视频时,下一个视频需要数秒才能完成渲染加载。希望实现当前视频播放时预加载下一个视频,使其在切换时即可就绪,求实现思路与建议。
当前代码
import SwiftUI import WebKit import UIKit struct AllVideoView: View { @State private var selected = "" @State private var arr = ["-q6-DxWZnlQ", "Bp3iu47RRJQ", "lXJdgDjw1Ks", "It3ecCpuzgc", "7WNJjr8QM1w"] var body: some View { ZStack { Color.black.edgesIgnoringSafeArea([.bottom, .top]) TabView(selection: $selected){ ForEach(arr, id: \.self){ id in SingleVideoView(link: id).tag(id) } .rotationEffect(.init(degrees: -90)) .frame(width: widthOrHeight(width: true), height: widthOrHeight(width: false)) } .offset(x: -10.5) .frame(width: widthOrHeight(width: false), height: widthOrHeight(width: true)) .rotationEffect(.init(degrees: 90)) .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) } } } struct SingleVideoView: View { let link: String @State private var viewIsShowing = false @State private var isVideoPlaying = false var body: some View { ZStack { Color.black SmartReelView(link: link, isPlaying: $isVideoPlaying, viewIsShowing: $viewIsShowing) Button("", action: {}).disabled(true) Color.gray.opacity(0.001) .onTapGesture { isVideoPlaying.toggle() } } .ignoresSafeArea() .onDisappear { isVideoPlaying = false viewIsShowing = false } .onAppear { viewIsShowing = true isVideoPlaying = true } } } struct SmartReelView: UIViewRepresentable { let link: String @Binding var isPlaying: Bool @Binding var viewIsShowing: Bool func makeCoordinator() -> Coordinator { Coordinator(self) } func makeUIView(context: Context) -> WKWebView { let webConfiguration = WKWebViewConfiguration() webConfiguration.allowsInlineMediaPlayback = true let webView = WKWebView(frame: .zero, configuration: webConfiguration) webView.navigationDelegate = context.coordinator let userContentController = WKUserContentController() webView.configuration.userContentController = userContentController loadInitialContent(in: webView) return webView } func updateUIView(_ uiView: WKWebView, context: Context) { var jsString = """ isPlaying = \((isPlaying) ? "true" : "false"); watchPlayingState(); """ uiView.evaluateJavaScript(jsString, completionHandler: nil) } class Coordinator: NSObject, WKNavigationDelegate { var parent: SmartReelView init(_ parent: SmartReelView) { self.parent = parent } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { if self.parent.viewIsShowing { webView.evaluateJavaScript("clickReady()", completionHandler: nil) } } } private func loadInitialContent(in webView: WKWebView) { let embedHTML = """ <style> body { margin: 0; background-color: black; } .iframe-container iframe { top: 0; left: 0; width: 100%; height: 100%; } </style> <div class="iframe-container"> <div id="player"></div> </div> <script> var tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); var player; var isPlaying = false; function onYouTubeIframeAPIReady() { player = new YT.Player('player', { width: '100%', videoId: '\(link)', playerVars: { 'playsinline': 1, 'controls': 0}, events: { 'onStateChange': function(event) { if (event.data === YT.PlayerState.ENDED) { player.seekTo(0); player.playVideo(); } } } }); } function clickReady() { player.playVideo(); } function watchPlayingState() { if (isPlaying) { player.playVideo(); } else { player.pauseVideo(); } } </script> """ webView.scrollView.isScrollEnabled = false webView.loadHTMLString(embedHTML, baseURL: nil) } } func widthOrHeight(width: Bool) -> CGFloat { let scenes = UIApplication.shared.connectedScenes let windowScene = scenes.first as? UIWindowScene let window = windowScene?.windows.first if width { return window?.screen.bounds.width ?? 0 } else { return window?.screen.bounds.height ?? 0 } }
实现思路与代码修改
核心思路
当前TabView仅在视图即将显示时才初始化SingleVideoView,导致视频加载延迟。解决方案是手动管理预加载队列,在当前视频播放时提前初始化并加载下一个视频的WebView资源,但不播放,切换时直接触发播放。
具体修改步骤
1. 跟踪当前视频索引并维护预加载缓存
在AllVideoView中添加当前索引跟踪和预加载视图缓存,提前创建下一个视频的视图实例:
struct AllVideoView: View { @State private var selected = "" @State private var arr = ["-q6-DxWZnlQ", "Bp3iu47RRJQ", "lXJdgDjw1Ks", "It3ecCpuzgc", "7WNJjr8QM1w"] @State private var currentIndex = 0 @State private var preloadedViews: [String: SingleVideoView] = [:] var body: some View { ZStack { Color.black.edgesIgnoringSafeArea([.bottom, .top]) TabView(selection: $selected){ ForEach(arr, id: \.self){ id in preloadedViews[id, default: SingleVideoView(link: id, preload: false)] .tag(id) .onAppear { currentIndex = arr.firstIndex(of: id)! preloadNextVideo() } } .rotationEffect(.init(degrees: -90)) .frame(width: widthOrHeight(width: true), height: widthOrHeight(width: false)) } .offset(x: -10.5) .frame(width: widthOrHeight(width: false), height: widthOrHeight(width: true)) .rotationEffect(.init(degrees: 90)) .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) .onAppear { // 初始化时预加载前两个视频 preloadedViews[arr[0]] = SingleVideoView(link: arr[0], preload: false) if arr.count > 1 { preloadedViews[arr[1]] = SingleVideoView(link: arr[1], preload: true) } } } } private func preloadNextVideo() { let nextIndex = (currentIndex + 1) % arr.count let nextId = arr[nextIndex] if preloadedViews[nextId] == nil { preloadedViews[nextId] = SingleVideoView(link: nextId, preload: true) } // 可选:清理已播放过的旧视频缓存,避免内存占用过高 let prevIndex = (currentIndex - 1 + arr.count) % arr.count let prevId = arr[prevIndex] if prevId != nextId { preloadedViews.removeValue(forKey: prevId) } } }
2. 修改SingleVideoView支持预加载模式
添加预加载标志,控制视图加载时机,预加载时仅加载资源不播放:
struct SingleVideoView: View { let link: String @State private var viewIsShowing = false @State private var isVideoPlaying = false let preload: Bool init(link: String, preload: Bool = false) { self.link = link self.preload = preload // 预加载时提前初始化WebView,但不播放 _viewIsShowing = State(initialValue: preload) _isVideoPlaying = State(initialValue: false) } var body: some View { ZStack { Color.black SmartReelView(link: link, isPlaying: $isVideoPlaying, viewIsShowing: $viewIsShowing) Button("", action: {}).disabled(true) Color.gray.opacity(0.001) .onTapGesture { isVideoPlaying.toggle() } } .ignoresSafeArea() .onDisappear { isVideoPlaying = false if !preload { viewIsShowing = false } } .onAppear { if !preload { viewIsShowing = true isVideoPlaying = true } } } }
3. 调整SmartReelView的加载逻辑
修改Coordinator,预加载时仅加载视频资源不自动播放:
class Coordinator: NSObject, WKNavigationDelegate { var parent: SmartReelView init(_ parent: SmartReelView) { self.parent = parent } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { guard self.parent.viewIsShowing else { return } if self.parent.isPlaying { webView.evaluateJavaScript("clickReady()", completionHandler: nil) } else { // 预加载时仅缓存视频资源,不播放 webView.evaluateJavaScript("player.cueVideoById('\(parent.link)')", completionHandler: nil) } } }
4. 优化YouTube播放器参数
在HTML中添加autoplay: 0,避免预加载时自动播放:
player = new YT.Player('player', { width: '100%', videoId: '\(link)', playerVars: { 'playsinline': 1, 'controls': 0, 'autoplay': 0}, events: { 'onStateChange': function(event) { if (event.data === YT.PlayerState.ENDED) { player.seekTo(0); player.playVideo(); } } } });
注意事项
- 控制预加载数量:建议仅预加载当前视频的下一个(或前后各一个),避免内存占用过高。
- 缓存清理:在切换视频时移除已播放且短时间内不会再次访问的视频视图,释放资源。
- 网络适配:弱网环境下可适当调整预加载策略,避免影响当前视频播放流畅度。
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

