-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
autoplay after recycle remove all items from AVPlayer queue recurururururursion use managers in the view add prefetch make sure player items stay in order add controller and item managers start of the view create module, ios
- Loading branch information
Showing
13 changed files
with
385 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"platforms": ["ios", "tvos", "android", "web"], | ||
"ios": { | ||
"modules": ["ExpoBlueskyVideoPlayerModule"] | ||
}, | ||
"android": { | ||
"modules": ["expo.modules.blueskyvideoplayer.ExpoBlueskyVideoPlayerModule"] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {VideoPlayer} from './src/VideoPlayer' |
21 changes: 21 additions & 0 deletions
21
modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayer.podspec
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
Pod::Spec.new do |s| | ||
s.name = 'ExpoBlueskyVideoPlayer' | ||
s.version = '1.0.0' | ||
s.summary = 'A sample project summary' | ||
s.description = 'A sample project description' | ||
s.author = '' | ||
s.homepage = 'https://docs.expo.dev/modules/' | ||
s.platforms = { :ios => '13.4', :tvos => '13.4' } | ||
s.source = { git: '' } | ||
s.static_framework = true | ||
|
||
s.dependency 'ExpoModulesCore' | ||
|
||
# Swift/Objective-C compatibility | ||
s.pod_target_xcconfig = { | ||
'DEFINES_MODULE' => 'YES', | ||
'SWIFT_COMPILATION_MODE' => 'wholemodule' | ||
} | ||
|
||
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" | ||
end |
35 changes: 35 additions & 0 deletions
35
modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerModule.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import ExpoModulesCore | ||
|
||
public class ExpoBlueskyVideoPlayerModule: Module { | ||
public func definition() -> ModuleDefinition { | ||
Name("ExpoBlueskyVideoPlayer") | ||
|
||
AsyncFunction("setShouldAutoplayAsync") { (value: Bool) in | ||
|
||
} | ||
|
||
AsyncFunction("prefetchAsync") { (source: String) in | ||
PlayerItemManager.shared.getOrAddItem(source: source) | ||
} | ||
|
||
View(ExpoBlueskyVideoPlayerView.self) { | ||
Events(["onLoad"]) | ||
|
||
Prop("source") { (view: ExpoBlueskyVideoPlayerView, prop: String) in | ||
view.source = prop | ||
} | ||
|
||
AsyncFunction("getIsPlayingAsync") { (view: ExpoBlueskyVideoPlayerView, promise: Promise) in | ||
promise.resolve(view.isPlaying) | ||
} | ||
|
||
AsyncFunction("playAsync") { (view: ExpoBlueskyVideoPlayerView) in | ||
view.play() | ||
} | ||
|
||
AsyncFunction("pauseAsync") { (view: ExpoBlueskyVideoPlayerView) in | ||
view.pause() | ||
} | ||
} | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import ExpoModulesCore | ||
|
||
public class ExpoBlueskyVideoPlayerView: ExpoView, AVPlayerViewControllerDelegate { | ||
public var source: String? = nil | ||
public var isPlaying: Bool = true | ||
|
||
private var controller: PlayerController? = nil | ||
|
||
public override var bounds: CGRect { | ||
didSet { | ||
if let controller = controller { | ||
controller.setFrame(rect: bounds) | ||
} | ||
} | ||
} | ||
|
||
public required init(appContext: AppContext? = nil) { | ||
super.init(appContext: appContext) | ||
self.clipsToBounds = true | ||
} | ||
|
||
public override func willMove(toWindow newWindow: UIWindow?) { | ||
if newWindow != nil { | ||
guard let source = self.source else { | ||
return | ||
} | ||
|
||
if let controller = PlayerControllerManager.shared.getPlayer() { | ||
controller.setFrame(rect: bounds) | ||
controller.initForView(source, view: self) | ||
self.addSubview(controller.view) | ||
self.controller = controller | ||
} | ||
} else { | ||
self.controller?.release() | ||
} | ||
} | ||
|
||
func play() { | ||
self.isPlaying = true | ||
self.controller?.play() | ||
} | ||
|
||
func pause() { | ||
self.isPlaying = false | ||
self.controller?.pause() | ||
} | ||
} |
113 changes: 113 additions & 0 deletions
113
modules/expo-bluesky-video-player/ios/PlayerController.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import AVKit | ||
|
||
class PlayerController: AVPlayerViewController, AVPlayerViewControllerDelegate { | ||
private var playerLooper: AVPlayerLooper? = nil | ||
var source: String? = nil | ||
var _superview: ExpoBlueskyVideoPlayerView? = nil | ||
|
||
var isInUse = false | ||
var playerItem: AVPlayerItem? { | ||
get { | ||
return self.player?.currentItem | ||
} | ||
set { | ||
if newValue == nil { | ||
if let player = self.player as? AVQueuePlayer { | ||
player.removeAllItems() | ||
} | ||
} else { | ||
self.player?.replaceCurrentItem(with: newValue) | ||
} | ||
} | ||
} | ||
var itemStatus: AVPlayerItem.Status? { | ||
get { | ||
return self.playerItem?.status | ||
} | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { | ||
super.init(nibName: nil, bundle: nil) | ||
self.player = AVQueuePlayer() | ||
|
||
self.delegate = self | ||
self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] | ||
self.view.backgroundColor = .clear | ||
self.showsPlaybackControls = false | ||
self.allowsPictureInPicturePlayback = false | ||
self.entersFullScreenWhenPlaybackBegins = true | ||
self.updatesNowPlayingInfoCenter = false | ||
if #available(iOS 16.0, *) { | ||
self.allowsVideoFrameAnalysis = false | ||
} | ||
|
||
self.player?.actionAtItemEnd = .pause | ||
self.player?.volume = .zero | ||
} | ||
|
||
func setFrame(rect: CGRect) { | ||
self.view.frame = rect | ||
} | ||
|
||
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { | ||
if keyPath == "status", let playerItem = object as? AVPlayerItem { | ||
if playerItem.status == .readyToPlay, let player = self.player as? AVQueuePlayer { | ||
// GIFs frequently have some black frames at the end of the video. To account for that, we offset the duration by 100ms, | ||
// which should be enough frames to prevent a flicker. | ||
player.play() | ||
self.setLooper() | ||
} | ||
} | ||
} | ||
|
||
private func setLooper() { | ||
guard let player = self.player as? AVQueuePlayer, let playerItem = self.playerItem else { | ||
return | ||
} | ||
|
||
let duration = playerItem.duration | ||
self.playerLooper = AVPlayerLooper( | ||
player: player, | ||
templateItem: playerItem, | ||
timeRange: CMTimeRange( | ||
start: CMTime(value: 0, timescale: duration.timescale), | ||
duration: CMTime(value: duration.value - 100, timescale: 1000) | ||
) | ||
) | ||
} | ||
|
||
func initForView(_ source: String, view: ExpoBlueskyVideoPlayerView) { | ||
if let playerItem = PlayerItemManager.shared.getOrAddItem(source: source) { | ||
playerItem.addObserver(self, forKeyPath: "status", options: [.old, .new], context: nil) | ||
self.isInUse = true | ||
self.source = source | ||
self.playerItem = playerItem | ||
self._superview = view | ||
|
||
if view.isPlaying, self.itemStatus == .readyToPlay { | ||
self.play() | ||
self.setLooper() | ||
} | ||
} | ||
} | ||
|
||
func release() { | ||
self.isInUse = false | ||
self.pause() | ||
self.source = nil | ||
self.playerLooper = nil | ||
self.playerItem = nil | ||
} | ||
|
||
func play() { | ||
self.player?.play() | ||
} | ||
|
||
func pause() { | ||
self.player?.pause() | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
modules/expo-bluesky-video-player/ios/PlayerControllerManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import AVKit | ||
|
||
class PlayerControllerManager { | ||
static let shared = PlayerControllerManager() | ||
|
||
private var controllers: [PlayerController] = [] | ||
private let max = 10 | ||
|
||
func getPlayer() -> PlayerController? { | ||
if let controller = self.controllers.first(where: { $0.isInUse == false }) { | ||
return controller | ||
} else if controllers.count < self.max { | ||
let controller = PlayerController() | ||
controllers.append(controller) | ||
return controller | ||
} | ||
return nil | ||
} | ||
|
||
func releasePlayer(controller: PlayerController) { | ||
if let controller = controllers.first(where: { $0 === controller }) { | ||
controller.release() | ||
} | ||
} | ||
|
||
func findBySource(source: String) -> PlayerController? { | ||
if let controller = controllers.first(where: { $0.source == source}) { | ||
return controller | ||
} | ||
return nil | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
modules/expo-bluesky-video-player/ios/PlayerItemManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import AVKit | ||
|
||
class PlayerItemManager { | ||
static let shared = PlayerItemManager() | ||
|
||
private var items: [(String, AVPlayerItem)] = [] | ||
private let max = 30 | ||
|
||
private func addItem(source: String) -> AVPlayerItem? { | ||
guard let url = URL(string: source) else { | ||
return nil | ||
} | ||
|
||
if items.count >= self.max { | ||
let first = self.items.removeFirst() | ||
self.removeItem(source: first.0) | ||
} | ||
|
||
let item = AVPlayerItem(url: url) | ||
self.items.append((source, item)) | ||
|
||
return item | ||
} | ||
|
||
func getOrAddItem(source: String) -> AVPlayerItem? { | ||
if let index = items.firstIndex(where: { $0.0 == source }) { | ||
let item = self.items[index] | ||
self.items.move(fromOffsets: IndexSet(integer: index), toOffset: self.items.count - 1) | ||
return item.1 | ||
} | ||
|
||
return self.addItem(source: source) | ||
} | ||
|
||
func removeItem(source: String?) { | ||
guard let source = source else { | ||
return | ||
} | ||
|
||
if let controller = PlayerControllerManager.shared.findBySource(source: source) { | ||
controller.release() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import ExpoModulesCore | ||
|
||
struct OnLoadEvent: Record { | ||
@Field | ||
var height = 0 | ||
@Field | ||
var width = 0 | ||
@Field | ||
var duration = 0 | ||
} |
15 changes: 15 additions & 0 deletions
15
modules/expo-bluesky-video-player/src/NativeVideoPlayer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import React from 'react' | ||
import {requireNativeViewManager} from 'expo-modules-core' | ||
|
||
import {VideoPlayerViewProps} from './VideoPlayer.types' | ||
|
||
const NativeView: React.ComponentType<VideoPlayerViewProps> = | ||
requireNativeViewManager('ExpoBlueskyVideoPlayer') | ||
|
||
export default React.forwardRef<VideoPlayerViewProps>( | ||
// @ts-ignore TODO type these later | ||
function NativeVideoPlayer(props: VideoPlayerViewProps, ref) { | ||
// @ts-ignore | ||
return <NativeView {...props} ref={ref} /> | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React, {createRef} from 'react' | ||
import {requireNativeModule} from 'expo-modules-core' | ||
|
||
import NativeVideoPlayer from './NativeVideoPlayer' | ||
import {VideoPlayerViewProps} from './VideoPlayer.types' | ||
|
||
const VideoModule = requireNativeModule('ExpoBlueskyVideoPlayer') | ||
|
||
export class VideoPlayer extends React.PureComponent<VideoPlayerViewProps> { | ||
nativeRef: React.RefObject<any> | ||
|
||
constructor(props: VideoPlayerViewProps | Readonly<VideoPlayerViewProps>) { | ||
super(props) | ||
this.nativeRef = createRef() | ||
} | ||
|
||
static async setShouldAutoplayAsync(shouldAutoplay: boolean): Promise<void> { | ||
await VideoModule.setShouldAutoplayAsync(shouldAutoplay) | ||
} | ||
|
||
async playAsync(): Promise<void> { | ||
await this.nativeRef.current.playAsync() | ||
} | ||
|
||
async pauseAsync(): Promise<void> { | ||
await this.nativeRef.current.pauseAsync() | ||
} | ||
|
||
render() { | ||
return <NativeVideoPlayer {...this.props} ref={this.nativeRef} /> | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
modules/expo-bluesky-video-player/src/VideoPlayer.types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {ViewProps} from 'react-native' | ||
|
||
export interface VideoPlayerLoadEvent { | ||
height: number | ||
width: number | ||
duration: number | ||
} | ||
|
||
export interface VideoPlayerViewProps extends ViewProps { | ||
source: string | null | ||
onLoad: (event: VideoPlayerLoadEvent) => void | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import * as React from 'react' | ||
|
||
import {ExpoBlueskyVideoPlayerViewProps} from './ExpoBlueskyVideoPlayer.types' | ||
|
||
export default function ExpoBlueskyVideoPlayerView( | ||
props: ExpoBlueskyVideoPlayerViewProps, | ||
) { | ||
return ( | ||
<div> | ||
<span>{props.name}</span> | ||
</div> | ||
) | ||
} |