diff --git a/.swiftlint.yml b/.swiftlint.yml
index aa8552ddb..4a86d8da0 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -13,6 +13,7 @@ disabled_rules:
   - syntactic_sugar
   - for_where
   - legacy_constant
+  - non_optional_string_data_conversion
 
 opt_in_rules:
   - attributes
diff --git a/DarockBili.xcodeproj/project.pbxproj b/DarockBili.xcodeproj/project.pbxproj
index 0527b947c..c3fdece15 100644
--- a/DarockBili.xcodeproj/project.pbxproj
+++ b/DarockBili.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_TEAM = B57D8PP775;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_FILE = SafariExtension/Info.plist;
@@ -1245,7 +1245,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_TEAM = B57D8PP775;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_FILE = SafariExtension/Info.plist;
@@ -1342,7 +1342,7 @@
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBiliAlternative.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\"";
 				DEVELOPMENT_TEAM = B38QUJMY47;
 				ENABLE_PREVIEWS = YES;
@@ -1387,7 +1387,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview\\ Content\"";
 				DEVELOPMENT_TEAM = B38QUJMY47;
 				ENABLE_PREVIEWS = YES;
@@ -1423,7 +1423,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_TEAM = B57D8PP775;
 				GENERATE_INFOPLIST_FILE = YES;
 				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
@@ -1447,7 +1447,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_TEAM = B38QUJMY47;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_FILE = SafariExtension/Info.plist;
@@ -1515,7 +1515,7 @@
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBili.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\"";
 				DEVELOPMENT_TEAM = B57D8PP775;
 				ENABLE_PREVIEWS = YES;
@@ -1562,7 +1562,7 @@
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBili.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\"";
 				DEVELOPMENT_TEAM = B57D8PP775;
 				ENABLE_PREVIEWS = YES;
@@ -1607,7 +1607,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview\\ Content\"";
 				DEVELOPMENT_TEAM = B57D8PP775;
 				ENABLE_PREVIEWS = YES;
@@ -1643,7 +1643,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview\\ Content\"";
 				DEVELOPMENT_TEAM = B57D8PP775;
 				ENABLE_PREVIEWS = YES;
@@ -1679,7 +1679,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_TEAM = B57D8PP775;
 				GENERATE_INFOPLIST_FILE = YES;
 				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
@@ -1702,7 +1702,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1262;
+				CURRENT_PROJECT_VERSION = 1279;
 				DEVELOPMENT_TEAM = B57D8PP775;
 				GENERATE_INFOPLIST_FILE = YES;
 				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
diff --git a/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index ad5c83ab7..34a131b8e 100644
--- a/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -33,8 +33,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/adamzarn/AZVideoPlayer",
       "state" : {
-        "revision" : "f37c779bf5c00e85dc31ad25d883b3f2e5242b2a",
-        "version" : "1.2.0"
+        "revision" : "75e9e05b87d2bd60d83be4b8db280eb2859a86bc",
+        "version" : "1.3.0"
       }
     },
     {
@@ -42,8 +42,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/Serene-Garden/Cepheus",
       "state" : {
-        "revision" : "38adeadadc1b52a61db4078a80aac98af3311890",
-        "version" : "2.0.2"
+        "revision" : "0754853c69a39f6cdc04abb8b1d194a3ed3a483e",
+        "version" : "2.3.0"
       }
     },
     {
@@ -52,7 +52,7 @@
       "location" : "https://github.com/Darock-Studio/DarockKit",
       "state" : {
         "branch" : "main",
-        "revision" : "0735d98596b3fe5280a3d287264150d4ae202076"
+        "revision" : "4afcdeba8f3c6ca4dbd7ba2886924bdcbadec262"
       }
     },
     {
@@ -97,7 +97,7 @@
       "location" : "https://github.com/mixpanel/mixpanel-swift",
       "state" : {
         "branch" : "master",
-        "revision" : "1ae92448ec3b0841ae5ceaa151c98ae66776596a"
+        "revision" : "e7869c433ee01cdc52aa14abf636fe0b412d72f8"
       }
     },
     {
@@ -105,8 +105,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/RickeyBoy/ScreenshotableView",
       "state" : {
-        "revision" : "3c140215fb12c7e8aef78e853006110350455c5b",
-        "version" : "1.0.0"
+        "revision" : "4dcc274e518756c9bb6eabc153b2edb51a7bd482",
+        "version" : "1.1.0"
       }
     },
     {
@@ -114,8 +114,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/SDWebImage/SDWebImage.git",
       "state" : {
-        "revision" : "f6afa0132961d593f07970d84e2d8b588c29ea04",
-        "version" : "5.19.1"
+        "revision" : "be0bcd7823ce56629948491f2eaeaa19979514f7",
+        "version" : "5.19.4"
       }
     },
     {
diff --git a/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme b/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme
index 2be1ee1d0..2188ba19d 100644
--- a/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme	
+++ b/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme	
@@ -96,8 +96,7 @@
       ignoresPersistentStateOnLaunch = "NO"
       debugDocumentVersioning = "YES"
       debugServiceExtension = "internal"
-      allowLocationSimulation = "YES"
-      notificationPayloadFile = "PushNotificationPayload.apns">
+      allowLocationSimulation = "YES">
       <BuildableProductRunnable
          runnableDebuggingMode = "0">
          <BuildableReference
diff --git a/Localizable.xcstrings b/Localizable.xcstrings
index 7b154a6c1..a54a77066 100644
--- a/Localizable.xcstrings
+++ b/Localizable.xcstrings
@@ -8863,7 +8863,6 @@
 
     },
     "手记建议" : {
-      "extractionState" : "stale",
       "localizations" : {
         "zh-Hant" : {
           "stringUnit" : {
@@ -8915,9 +8914,6 @@
     },
     "播放倍速" : {
 
-    },
-    "播放卡住/无画面?" : {
-
     },
     "文件保险箱" : {
 
@@ -9498,9 +9494,6 @@
     },
     "通知" : {
 
-    },
-    "重启 Apple Watch" : {
-
     },
     "重新载入" : {
       "localizations" : {
diff --git a/MeowBili/InMain/SearchView.swift b/MeowBili/InMain/SearchView.swift
index 292eab4ba..89a882b2a 100644
--- a/MeowBili/InMain/SearchView.swift
+++ b/MeowBili/InMain/SearchView.swift
@@ -34,6 +34,7 @@ struct SearchMainView: View {
     @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = ""
     @AppStorage("SESSDATA") var sessdata = ""
     @AppStorage("bili_jct") var biliJct = ""
+    @AppStorage("IsShowHotsInSearch") var isShowHotsInSearch = true
     @State var searchText = ""
     @State var isSearchPresented = false
     @State var searchHistory = [String]()
@@ -76,7 +77,7 @@ struct SearchMainView: View {
                     #endif
                 }
             }
-            if !hotSearches.isEmpty {
+            if !hotSearches.isEmpty && isShowHotsInSearch {
                 Section {
                     if !isHotSearchFolded {
                         ForEach(0..<hotSearches.count, id: \.self) { i in
diff --git a/MeowBili/Others/SettingsView.swift b/MeowBili/Others/SettingsView.swift
index c7d4f4150..1c54c7c28 100644
--- a/MeowBili/Others/SettingsView.swift
+++ b/MeowBili/Others/SettingsView.swift
@@ -482,10 +482,12 @@ struct SuggestionViewSettingsView: View {
 
 struct NetworkSettingsView: View {
     @AppStorage("IsShowVideoSuggestionsFromDarock") var isShowVideoSuggestionsFromDarock = true
+    @AppStorage("IsShowHotsInSearch") var isShowHotsInSearch = true
     @AppStorage("IsShowNetworkFixing") var isShowNetworkFixing = true
     var body: some View {
         List {
             Section {
+                Toggle("在搜索页显示热搜", isOn: $isShowHotsInSearch)
                 Toggle("显示来自 Darock 的推荐", isOn: $isShowVideoSuggestionsFromDarock)
             }
             Section {
diff --git a/MeowBili/PersonalCenter/UserDetailView.swift b/MeowBili/PersonalCenter/UserDetailView.swift
index 3c8bde375..611afe7ff 100644
--- a/MeowBili/PersonalCenter/UserDetailView.swift
+++ b/MeowBili/PersonalCenter/UserDetailView.swift
@@ -1077,8 +1077,8 @@ struct UserDetailView: View {
                     RefreshVideos()
                 }
             }
-            .onChange(of: viewSelector) { value in
-                switch value {
+            .onChange(of: viewSelector) { _ in
+                switch viewSelector {
                 case .video:
                     break
                 case .article:
diff --git a/MeowBili/Video/VideoDetailView.swift b/MeowBili/Video/VideoDetailView.swift
index ba1b91177..0fc96987e 100644
--- a/MeowBili/Video/VideoDetailView.swift
+++ b/MeowBili/Video/VideoDetailView.swift
@@ -611,7 +611,7 @@ struct VideoDetailView: View {
                 }
                 .sheet(isPresented: $isVideoPlayerPresented, content: {
                     VideoPlayerView(videoDetails: $videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID)
-                        .navigationBarHidden(true)
+                        .toolbar(.hidden)
                 })
                 .containerBackground(for: .navigation) {
                     if !isInLowBatteryMode {
@@ -1103,7 +1103,7 @@ struct VideoDetailView: View {
                     })
                     .sheet(isPresented: $isVideoPlayerPresented, content: {
                         VideoPlayerView(videoDetails: $videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID)
-                            .navigationBarHidden(true)
+                            .toolbar(.hidden)
                     })
                     Button(action: {
                         isMoreMenuPresented = true
@@ -1511,7 +1511,7 @@ struct VideoDetailView: View {
             .sheet(isPresented: $isDownloadPresented, content: { VideoDownloadView(bvid: videoDetails["BV"]!, videoDetails: videoDetails, isPaged: true) })
             .sheet(isPresented: $isVideoPlayerPresented, content: {
                 VideoPlayerView(videoDetails: $videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID)
-                    .navigationBarHidden(true)
+                    .toolbar(.hidden)
             })
         }
     }
diff --git a/MeowBili/Video/VideoPlayerView.swift b/MeowBili/Video/VideoPlayerView.swift
index 7bce17eb9..f3c512e1b 100644
--- a/MeowBili/Video/VideoPlayerView.swift
+++ b/MeowBili/Video/VideoPlayerView.swift
@@ -19,6 +19,7 @@
 import AVKit
 import SwiftUI
 import Dynamic
+import Combine
 import DarockKit
 import Alamofire
 import AVFoundation
@@ -62,9 +63,8 @@ struct VideoPlayerView: View {
     #endif
     @State var currentTime: Double = 0.0
     @State var playerTimer: Timer?
-    @State var danmakuTimer: Timer?
     @State var playProgressTimer: Timer?
-    @State var player: AVPlayer!
+    @State var player: AVPlayer! = AVPlayer()
     @State var isFinishedInit = false
     @State var willBeginFullScreenPresentation = false
     @State var showDanmakus = [[String: String]]()
@@ -163,7 +163,7 @@ struct VideoPlayerView: View {
                                     }
                                     .allowsHitTesting(false)
                                     .offset(x: -danmakuOffset)
-                                    .animation(.smooth, value: danmakuOffset)
+//                                    .animation(.linear, value: danmakuOffset)
                                 }
                             }
                             .rotationEffect(.degrees(isFullScreen ? 90 : 0))
@@ -184,14 +184,14 @@ struct VideoPlayerView: View {
                     }
                     Section {
                         Picker("播放倍速", selection: $videoSpeed) {
-                            Text("1x").tag(1.0)
-                            Text("1.5x").tag(1.5)
-                            Text("2x").tag(2.0)
-                            Text("3x").tag(3.0)
-                            Text("5x").tag(5.0)
+                            Text("1x").tag(Float(1.0))
+                            Text("1.5x").tag(Float(1.5))
+                            Text("2x").tag(Float(2.0))
+                            Text("3x").tag(Float(3.0))
+                            Text("5x").tag(Float(5.0))
                         }
-                        .onChange(of: videoSpeed) { value in
-                            player.rate = value
+                        .onChange(of: videoSpeed) {
+                            player.rate = videoSpeed
                         }
                         Button(action: {
                             player.seek(to: CMTime(seconds: currentTime + 10, preferredTimescale: 1))
@@ -206,17 +206,6 @@ struct VideoPlayerView: View {
                     } header: {
                         Text("播放")
                     }
-                    Section {
-                        Button(role: .destructive, action: {
-                            let window = (Dynamic.UIApplication.sharedApplication.windows.asArray! as! [Any]).first!
-                            let dwindow = Dynamic(window)
-                            while true { dwindow.snapshotView(afterScreenUpdates: false) }
-                        }, label: {
-                            Text("重启 Apple Watch")
-                        })
-                    } header: {
-                        Text("播放卡住/无画面?")
-                    }
                 }
                 .tag(2)
             }
@@ -264,10 +253,10 @@ struct VideoPlayerView: View {
                 
                 player.seek(to: CMTime(seconds: UserDefaults.standard.double(forKey: "\(videoBvid)\(videoCID)PlayTime"), preferredTimescale: 1))
                 
-                if let coverData = try? Data(contentsOf: URL(string: videoDetails["Pic"] ?? "") ?? URL(string: "http://example.com")!) {
-                    let cover = UIImage(data: coverData) ?? UIImage()
-                    NowPlayingExtension.setPlayingInfoTitle(videoDetails["Title"]!, artist: videoDetails["UP"]!, artwork: cover)
-                }
+//                if let coverData = try? Data(contentsOf: URL(string: videoDetails["Pic"] ?? "") ?? URL(string: "http://example.com")!) {
+//                    let cover = UIImage(data: coverData) ?? UIImage()
+//                    NowPlayingExtension.setPlayingInfoTitle(videoDetails["Title"]!, artist: videoDetails["UP"]!, artwork: cover)
+//                }
                 
                 let headers: HTTPHeaders = [
                     "cookie": "SESSDATA=\(sessdata)"
@@ -299,13 +288,6 @@ struct VideoPlayerView: View {
                 
                 UpdateDanmaku()
                 
-                danmakuTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in
-                    danmakuOffset = player.currentTime().seconds * 50
-                    #if !os(watchOS)
-                    currentPlayTime = player.currentTime().seconds
-                    #endif
-                }
-                
                 playProgressTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in
                     if (player.currentItem?.duration.seconds ?? 0) - player.currentTime().seconds > 10 {
                         UserDefaults.standard.set(player.currentTime().seconds, forKey: "\(videoBvid)\(videoCID)PlayTime")
@@ -329,7 +311,6 @@ struct VideoPlayerView: View {
             }
             #endif
             playerTimer?.invalidate()
-            danmakuTimer?.invalidate()
             playProgressTimer?.invalidate()
             player?.pause()
             if (player.currentItem?.duration.seconds ?? 0) - player.currentTime().seconds > 10 {
@@ -338,8 +319,8 @@ struct VideoPlayerView: View {
                 UserDefaults.standard.removeObject(forKey: "\(videoBvid)\(videoCID)PlayTime")
             }
         }
-        .onChange(of: videoLink) { value in
-            let asset = AVURLAsset(url: URL(string: value)!, options: ["AVURLAssetHTTPHeaderFieldsKey": ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "Referer": "https://www.bilibili.com"]])
+        .onChange(of: videoLink) {
+            let asset = AVURLAsset(url: URL(string: videoLink)!, options: ["AVURLAssetHTTPHeaderFieldsKey": ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "Referer": "https://www.bilibili.com"]])
             let item = AVPlayerItem(asset: asset)
             player?.pause()
             player = nil
@@ -351,9 +332,16 @@ struct VideoPlayerView: View {
             danmakuOffset = 0
             UpdateDanmaku()
         }
+        .onReceive(player.periodicTimePublisher()) { time in
+            danmakuOffset = time.seconds * 50
+            #if !os(watchOS)
+            currentPlayTime = time.seconds
+            #endif
+            currentTime = time.seconds
+        }
         #if !os(watchOS)
-        .onChange(of: shouldPause) { value in
-            if value == true {
+        .onChange(of: shouldPause) {
+            if shouldPause {
                 player?.pause()
                 shouldPause = false
             }
@@ -431,10 +419,7 @@ struct VideoPlayerView: View {
 #else
                     // Less danmakus for watch
                     if showDanmakus.count > 200 {
-                        for _ in 1...5000 {
-                            if showDanmakus.count <= 200 {
-                                break
-                            }
+                        while showDanmakus.count > 200 {
                             showDanmakus.remove(at: Int.random(in: 0..<showDanmakus.count))
                         }
                     }
@@ -472,3 +457,57 @@ struct VideoPlayerView: View {
     }
     #endif
 }
+
+// Extensions for periodicTimePublisher
+extension AVPlayer {
+    func periodicTimePublisher(forInterval interval: CMTime = CMTime(seconds: 0.5,
+                                                                     preferredTimescale: CMTimeScale(NSEC_PER_SEC))) -> AnyPublisher<CMTime, Never> {
+        Publisher(self, forInterval: interval)
+            .eraseToAnyPublisher()
+    }
+}
+fileprivate extension AVPlayer {
+    private struct Publisher: Combine.Publisher {
+        typealias Output = CMTime
+        typealias Failure = Never
+        
+        var player: AVPlayer
+        var interval: CMTime
+        
+        init(_ player: AVPlayer, forInterval interval: CMTime) {
+            self.player = player
+            self.interval = interval
+        }
+        
+        func receive<S>(subscriber: S) where S: Subscriber, Publisher.Failure == S.Failure, Publisher.Output == S.Input {
+            let subscription = CMTime.Subscription(subscriber: subscriber, player: player, forInterval: interval)
+            subscriber.receive(subscription: subscription)
+        }
+    }
+}
+fileprivate extension CMTime {
+    final class Subscription<SubscriberType: Subscriber>: Combine.Subscription where SubscriberType.Input == CMTime, SubscriberType.Failure == Never {
+        var player: AVPlayer?
+        var observer: Any?
+        
+        init(subscriber: SubscriberType, player: AVPlayer, forInterval interval: CMTime) {
+            self.player = player
+            observer = player.addPeriodicTimeObserver(forInterval: interval, queue: nil) { time in
+                _ = subscriber.receive(time)
+            }
+        }
+        
+        func request(_ demand: Subscribers.Demand) {
+            // We do nothing here as we only want to send events when they occur.
+            // See, for more info: https://developer.apple.com/documentation/combine/subscribers/demand
+        }
+        
+        func cancel() {
+            if let observer = observer {
+                player?.removeTimeObserver(observer)
+            }
+            observer = nil
+            player = nil
+        }
+    }
+}