{"id":32315516,"url":"https://github.com/zeroonet/zonplayer","last_synced_at":"2026-02-20T23:02:03.304Z","repository":{"id":208574365,"uuid":"713309453","full_name":"ZeroOnet/ZonPlayer","owner":"ZeroOnet","description":"ZonPlayer is a player library base on AVPlayer with cache and remote control support on iOS.","archived":false,"fork":false,"pushed_at":"2025-09-01T08:07:29.000Z","size":646,"stargazers_count":9,"open_issues_count":1,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-28T21:07:25.281Z","etag":null,"topics":["avplayer","cache","ios","resourceloader"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ZeroOnet.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"zeroonet","open_collective":"zonplayer"}},"created_at":"2023-11-02T09:06:07.000Z","updated_at":"2025-09-07T15:12:41.000Z","dependencies_parsed_at":"2024-06-20T14:19:25.512Z","dependency_job_id":"54054765-7a8d-4a95-a71e-79147df7dc60","html_url":"https://github.com/ZeroOnet/ZonPlayer","commit_stats":null,"previous_names":["zeroonet/zonplayer"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ZeroOnet/ZonPlayer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZeroOnet%2FZonPlayer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZeroOnet%2FZonPlayer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZeroOnet%2FZonPlayer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZeroOnet%2FZonPlayer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZeroOnet","download_url":"https://codeload.github.com/ZeroOnet/ZonPlayer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZeroOnet%2FZonPlayer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29667119,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-20T19:49:36.704Z","status":"ssl_error","status_checked_at":"2026-02-20T19:44:05.372Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["avplayer","cache","ios","resourceloader"],"created_at":"2025-10-23T10:48:08.919Z","updated_at":"2026-02-20T23:02:03.298Z","avatar_url":"https://github.com/ZeroOnet.png","language":"Swift","funding_links":["https://github.com/sponsors/zeroonet","https://opencollective.com/zonplayer"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"images/logo.png\" alt=\"ZonPlayer\" title=\"ZonPlayer\" width=\"500\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/ZeroOnet/ZonPlayer/actions/workflows/build.yaml\"\u003e\u003cimg src=\"https://github.com/ZeroOnet/ZonPlayer/actions/workflows/build.yaml/badge.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://codecov.io/gh/ZeroOnet/ZonPlayer\"\u003e\u003cimg src=\"https://codecov.io/gh/ZeroOnet/ZonPlayer/graph/badge.svg?token=3YD2FBEW4N\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://cocoapods.org/pods/ZonPlayer\"\u003e\u003cimg src=\"http://img.shields.io/cocoapods/v/ZonPlayer.svg?style=flat\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/ZeroOnet/ZonPlayer\"\u003e\u003cimg src=\"https://img.shields.io/badge/Carthage-compatible-brightgreen.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://swift.org/package-manager/\"\u003e\u003cimg src=\"https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://raw.githubusercontent.com/ZeroOnet/ZonPlayer/master/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-black\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nZonPlayer is a player library base on AVPlayer with cache and remote control support in iOS. For convenience, we defined interfaces can be called by chain.\n\n# Features\n\n- [x] Configure AVAudioSession asynchronously to prevent the main thread from hang.\n- [x] Support 3rd-party cache like VIMediaCache. There are presetted with `ZPC.Harvest` and `ZPC.Streaming(base on AVAssetResourceLoader)`.\n- [x] Manage now playing info and remote control command.\n- [x] Use plugin to intercept progress for streaming playback.\n- [x] Retry automatically if then player has an error, eg: media services were reset.\n\n# Usage\n\n```swift\n\n    let player: ZonPlayable = ZonPlayer.player(any URLConvertible)\n        .session(any ZonPlayer.Sessionable)\n        .cache(any ZonPlayer.Cacheable) // Conform ZonPlayer.Cacheable to customize cache category.\n        .remoteControl(self) { wlf, payload in // Conform ZonPlayer.RemoteControllable to customize background playback controller.\n            payload.title(String).artist(String)....\n        }\n        .onPaused(self) { wlf, payload in // Conform ZonPlayer.Observable to listen player.\n        }\n        .activate(in: ZonPlayerView)\n\n    // or\n    let player: ZonPlayable = ZonPlayer.player(any URLConvertible)\n        .on(\\.session, any ZonPlayer.Sessionable)\n        .on(\\.cache, any ZonPlayer.Cacheable)\n        .on(\\.finish, .init {  in })\n        .on(\\.pause, .init(on: self, block: { wlf, player in }))\n        .activate()\n\n    // Conform ZonPlayer.Controllable to control player instance.\n    player.pause()\n    player.play()\n    player.seek(to: 0)\n    // ...\n\n    // Conform ZonPlayer.Gettable to read player status.\n    player.currentTime\n    player.duration\n    player.url\n    // ...\n\n```\n\nIntegrate 3rd-party cache:\n\n```swift\n\nimport VIMediaCache\n\nfinal class TestCache: ZonPlayer.Cacheable {\n    let manager = VIResourceLoaderManager()\n\n    func prepare(url: URL, completion: @escaping (Result\u003cAVURLAsset, ZonPlayer.Error\u003e) -\u003e Void) {\n        let item = manager.playerItem(with: url).unsafelyUnwrapped\n        let asset = (item.asset as? AVURLAsset).unsafelyUnwrapped\n        completion(.success(asset))\n    }\n}\n\nfunc play() {\n    let player = ZonPlayer.player(url).cache(TestCache())\n}\n\n```\n\n**Notice:** before using ZPC.Streaming, it is advisable to ensure that the URL supports random access to avoid potential unexpected issues. Below is the official documentation explanation for `isByteRangeAccessSupported`:\n\u003e If this property is not true for resources that must be loaded incrementally, loading of the resource may fail. Such resources include anything that contains media data.\n\n# Compatibility\n\nIn iOS 18.0.1, we encountered an issue where video playback sometimes has no sound, occasionally accompanied by video stuttering. The example code is as follows:\n\n```swift\n\nfunc seekAndPause(to time: TimeInterval) {\n    _player?.seek(to: time) { [weak self] _ in\n        self?._player.pause()\n    }\n}\n\nlet time: TimeInterval = 10 // Any time\nseekAndPause(to: time)\nseekAndPause(to: time) // Call repeatedly\n```\n\nThe official documentation describes the `completionHandler` parameter for `AVPlayer().seek` is:\n\u003e The completion handler for any prior seek request that is still in process will be invoked immediately with the finished parameter set to false.\n\u003e If the new request completes without being interrupted by another seek request or by any other operation the specified completion handler will be invoked with the finished parameter set to true.\n\nEventually, we discovered that when the `completionHandler` invocation flag is set to false, playback control encounters this issue. Thus, the fix is quite simple:\n\n```swift\nfunc seekAndPause(to time: TimeInterval) {\n    _player?.seek(to: time) { [weak self] in\n        guard $0 else { return }\n        self?._player.pause()\n    }\n}\n```\n\n# Issues? Features!\n## AVPlayer Unexpectedly Pauses After View Controller is Popped\nScenario:\n- A UIViewController (Controller A) hosts an AVPlayer for video playback.\n- Background audio playback is enabled via AVAudioSession.\n- The app enters background → then returns to foreground.\n- Immediately after returning to foreground, Controller A is popped from the navigation stack.\n- The controller instance is retained manually and not deallocated.\n- ❗️ Unexpected behavior: AVPlayer pauses automatically after pop, even though play() was not interrupted and no errors were triggered.\n\nPossible Cause:\nWhen Controller A is popped, its view is removed from the view hierarchy. If the AVPlayerLayer is attached to self.view.layer, it may lose its rendering context.\nThis could cause AVPlayer to automatically pause, especially after returning from background, because the rendering engine may detect that there is no active video layer available.\n\nFix Example:\n```swift\nfinal class A: UIViewController {\n    override func viewWillAppear(_ animated: Bool) {\n        super.viewWillAppear(animated)\n        video.playerView.playerLayer.player = _stashedPlayer\n    }\n\n    override func viewWillDisappear(_ animated: Bool) {\n        super.viewWillDisappear(animated)\n        let playerLayer = video.playerView.playerLayer\n        _stashedPlayer = playerLayer?.player\n        playerLayer?.player = nil\n    }\n}\n```\n\n# Requirements\n\n- iOS 13.0 or later\n- Swift 6.0 or later\n\n# Installation\n\n## CocoaPods\n\n```ruby\nsource 'https://github.com/CocoaPods/Specs.git'\nplatform :ios, '13.0'\nuse_frameworks!\n\ntarget 'MyApp' do\n  pod 'ZonPlayer', '~\u003e 1.1.0'\nend\n\n```\n\n## Carthage\n\n```\ngithub \"ZeroOnet/ZonPlayer\" ~\u003e 1.0.0\n```\n\n## Swift Package Manager\n- File \u003e Swift Packages \u003e Add Package Dependency\n- Add `git@github.com:ZeroOnet/ZonPlayer.git`\n- Select \"Up to Next Major\" with \"1.0.0\"\n\n# Author\n\nZeroOnet, zeroonetworkspace@gmail.com\n\n# Blogs\n[从头撸一个播放器 I —— ZonPlayer 需求分析及接口设计](https://zeroonet.com/2023/11/22/zonplayer-part-1/) \u003cbr\u003e\n[从头撸一个播放器 II —— 音频会话、远程控制和播放器实现](https://zeroonet.com/2023/11/24/zonplayer-part-2/) \u003cbr\u003e\n[从头撸一个播放器 III —— 缓存](https://zeroonet.com/2023/12/01/zonplayer-part-3/) \u003cbr\u003e\n[从头撸一个播放器 IV(终) —— Github Action 与组件发布](https://zeroonet.com/2023/12/05/zonplayer-part-4/)\n\n# Reference\n[Alamofire](https://github.com/Alamofire/Alamofire)\u003cbr\u003e\n[Kingfisher](https://github.com/onevcat/Kingfisher)\u003cbr\u003e\n[VIMediaCache](https://github.com/vitoziv/VIMediaCache)\u003cbr\u003e\n\n# License\n\nZonPlayer is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzeroonet%2Fzonplayer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzeroonet%2Fzonplayer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzeroonet%2Fzonplayer/lists"}