ios live tv and experimental settings
This commit is contained in:
parent
5531c912ea
commit
d649dd88cf
|
@ -27,14 +27,14 @@ final class LiveTVVideoPlayerCoordinator: NavigationCoordinatable {
|
|||
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
// if Defaults[.Experimental.liveTVNativePlayer] {
|
||||
// LiveTVNativeVideoPlayerView(viewModel: viewModel)
|
||||
// .navigationBarHidden(true)
|
||||
// .ignoresSafeArea()
|
||||
// } else {
|
||||
if Defaults[.Experimental.liveTVNativePlayer] {
|
||||
LiveTVNativePlayerView(viewModel: viewModel)
|
||||
.navigationBarHidden(true)
|
||||
.ignoresSafeArea()
|
||||
} else {
|
||||
LiveTVPlayerView(viewModel: viewModel)
|
||||
.navigationBarHidden(true)
|
||||
.ignoresSafeArea()
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -263,6 +263,7 @@
|
|||
C4534981279A3F140045F1E2 /* tvOSLiveTVOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */; };
|
||||
C4534983279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534982279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift */; };
|
||||
C4534985279A40C60045F1E2 /* LiveTVVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534984279A40C50045F1E2 /* LiveTVVideoPlayerView.swift */; };
|
||||
C45640D0281A43EF007096DE /* LiveTVNativePlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45640CF281A43EF007096DE /* LiveTVNativePlayerViewController.swift */; };
|
||||
C45942C527F67DA400C54FE7 /* LiveTVCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */; };
|
||||
C45942C627F695FB00C54FE7 /* LiveTVProgramsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */; };
|
||||
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942C827F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift */; };
|
||||
|
@ -751,6 +752,7 @@
|
|||
C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tvOSLiveTVOverlay.swift; sourceTree = "<group>"; };
|
||||
C4534982279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tvOSLiveTVVideoPlayerCoordinator.swift; sourceTree = "<group>"; };
|
||||
C4534984279A40C50045F1E2 /* LiveTVVideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveTVVideoPlayerView.swift; sourceTree = "<group>"; };
|
||||
C45640CF281A43EF007096DE /* LiveTVNativePlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveTVNativePlayerViewController.swift; sourceTree = "<group>"; };
|
||||
C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVCoordinator.swift; sourceTree = "<group>"; };
|
||||
C45942C827F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSLiveTVVideoPlayerCoordinator.swift; sourceTree = "<group>"; };
|
||||
C45942CA27F6984100C54FE7 /* LiveTVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVPlayerViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -1742,6 +1744,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
E13AD72D2798BC8D00FDCEE8 /* NativePlayerViewController.swift */,
|
||||
C45640CF281A43EF007096DE /* LiveTVNativePlayerViewController.swift */,
|
||||
E1002B692793E12E00E47059 /* Overlays */,
|
||||
E1C812B5277A8E5D00918266 /* PlayerOverlayDelegate.swift */,
|
||||
E1C812B8277A8E5D00918266 /* VLCPlayerView.swift */,
|
||||
|
@ -2515,6 +2518,7 @@
|
|||
5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */,
|
||||
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */,
|
||||
E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */,
|
||||
C45640D0281A43EF007096DE /* LiveTVNativePlayerViewController.swift in Sources */,
|
||||
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
||||
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
|
||||
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.100",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.000",
|
||||
"red" : "0.000"
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
@ -40,10 +40,10 @@
|
|||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.100",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.000",
|
||||
"red" : "0.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "light"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.100",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.000",
|
||||
"red" : "0.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.100",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "light"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.250",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.000",
|
||||
"red" : "0.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.250",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "light"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.000",
|
||||
"red" : "0.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -62,97 +62,91 @@ struct LiveTVChannelItemWideElement: View {
|
|||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
HStack {
|
||||
ZStack(alignment: .center) {
|
||||
ImageView(channel.getPrimaryImage(maxWidth: 128))
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.padding(.init(top: 0, leading: 0, bottom: 8, trailing: 0))
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
.frame(maxHeight: .infinity)
|
||||
GeometryReader { gp in
|
||||
ZStack(alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.gray)
|
||||
.opacity(0.4)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 6, maxHeight: 6)
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(Color.jellyfinPurple)
|
||||
.frame(width: CGFloat(progressPercent * gp.size.width), height: 6)
|
||||
ZStack {
|
||||
HStack {
|
||||
ZStack(alignment: .center) {
|
||||
ImageView(channel.getPrimaryImage(maxWidth: 128))
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.padding(.init(top: 0, leading: 0, bottom: 8, trailing: 0))
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
.frame(maxHeight: .infinity)
|
||||
GeometryReader { gp in
|
||||
ZStack(alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.gray)
|
||||
.opacity(0.4)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 6, maxHeight: 6)
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(Color.jellyfinPurple)
|
||||
.frame(width: CGFloat(progressPercent * gp.size.width), height: 6)
|
||||
}
|
||||
}
|
||||
.frame(height: 6, alignment: .center)
|
||||
.padding(.init(top: 0, leading: 4, bottom: 0, trailing: 4))
|
||||
}
|
||||
.frame(height: 6, alignment: .center)
|
||||
.padding(.init(top: 0, leading: 4, bottom: 0, trailing: 4))
|
||||
}
|
||||
if loading {
|
||||
|
||||
ProgressView()
|
||||
|
||||
}
|
||||
}
|
||||
.aspectRatio(1.0, contentMode: .fit)
|
||||
VStack(alignment: .leading) {
|
||||
let channelNumber = channel.number != nil ? "\(channel.number ?? "") " : ""
|
||||
let channelName = "\(channelNumber)\(channel.name ?? "?")"
|
||||
Text(channelName)
|
||||
.font(.body)
|
||||
.lineLimit(1)
|
||||
.frame(alignment: .leading)
|
||||
HStack(alignment: .top) {
|
||||
Text(currentProgramText.timeDisplay)
|
||||
.font(.footnote)
|
||||
.lineLimit(2)
|
||||
.foregroundColor(.green)
|
||||
.frame(width: 40)
|
||||
Text(currentProgramText.title)
|
||||
.font(.footnote)
|
||||
.lineLimit(2)
|
||||
.foregroundColor(.green)
|
||||
}
|
||||
if nextProgramsText.count > 0,
|
||||
let nextItem = nextProgramsText[0] {
|
||||
HStack(alignment: .top) {
|
||||
Text(nextItem.timeDisplay)
|
||||
.font(.footnote)
|
||||
.lineLimit(2)
|
||||
.foregroundColor(.gray)
|
||||
.frame(width: 40)
|
||||
Text(nextItem.title)
|
||||
.font(.footnote)
|
||||
.lineLimit(2)
|
||||
.foregroundColor(.gray)
|
||||
if loading {
|
||||
|
||||
ProgressView()
|
||||
|
||||
}
|
||||
}
|
||||
if nextProgramsText.count > 1,
|
||||
let nextItem2 = nextProgramsText[1] {
|
||||
HStack(alignment: .top) {
|
||||
Text(nextItem2.timeDisplay)
|
||||
.font(.footnote)
|
||||
.lineLimit(2)
|
||||
.foregroundColor(.gray)
|
||||
.frame(width: 40)
|
||||
Text(nextItem2.title)
|
||||
.font(.footnote)
|
||||
.lineLimit(2)
|
||||
.foregroundColor(.gray)
|
||||
.aspectRatio(1.0, contentMode: .fit)
|
||||
VStack(alignment: .leading) {
|
||||
let channelNumber = channel.number != nil ? "\(channel.number ?? "") " : ""
|
||||
let channelName = "\(channelNumber)\(channel.name ?? "?")"
|
||||
Text(channelName)
|
||||
.font(.body)
|
||||
.lineLimit(1)
|
||||
.foregroundColor(Color.jellyfinPurple)
|
||||
.frame(alignment: .leading)
|
||||
.padding(.init(top: 0, leading: 0, bottom: 4, trailing: 0))
|
||||
programLabel(timeText: currentProgramText.timeDisplay, titleText: currentProgramText.title, color: Color("TextHighlightColor"))
|
||||
if nextProgramsText.count > 0,
|
||||
let nextItem = nextProgramsText[0] {
|
||||
programLabel(timeText: nextItem.timeDisplay, titleText: nextItem.title, color: Color.gray)
|
||||
}
|
||||
if nextProgramsText.count > 1,
|
||||
let nextItem2 = nextProgramsText[1] {
|
||||
programLabel(timeText: nextItem2.timeDisplay, titleText: nextItem2.title, color: Color.gray)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
.frame(alignment: .leading)
|
||||
.padding()
|
||||
.opacity(loading ? 0.5 : 1.0)
|
||||
}
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color("BackgroundSecondaryColor"))
|
||||
)
|
||||
.frame(height: 128)
|
||||
.onTapGesture {
|
||||
onSelect { loadingState in
|
||||
loading = loadingState
|
||||
}
|
||||
}
|
||||
.frame(alignment: .leading)
|
||||
.padding()
|
||||
.opacity(loading ? 0.5 : 1.0)
|
||||
}
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16, style: .continuous).fill(Color("BackgroundColor"))
|
||||
)
|
||||
.frame(height: 128)
|
||||
.onTapGesture {
|
||||
onSelect { loadingState in
|
||||
loading = loadingState
|
||||
}
|
||||
.background{
|
||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||
.fill(Color("BackgroundColor"))
|
||||
.shadow(color: Color("ShadowColor"), radius: 4, x: 0, y: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func programLabel(timeText: String, titleText: String, color: Color) -> some View {
|
||||
HStack(alignment: .top) {
|
||||
Text(timeText)
|
||||
.font(.footnote)
|
||||
.lineLimit(2)
|
||||
.foregroundColor(color)
|
||||
.frame(width: 38, alignment: .leading)
|
||||
Text(titleText)
|
||||
.font(.footnote)
|
||||
.lineLimit(2)
|
||||
.foregroundColor(color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ struct LiveTVChannelsView: View {
|
|||
)
|
||||
let groupSize = NSCollectionLayoutSize(
|
||||
widthDimension: .fractionalWidth(1.0),
|
||||
heightDimension: .absolute(132)
|
||||
heightDimension: .absolute(144)
|
||||
)
|
||||
let group = NSCollectionLayoutGroup.horizontal(
|
||||
layoutSize: groupSize,
|
||||
|
@ -114,7 +114,7 @@ struct LiveTVChannelsView: View {
|
|||
} else {
|
||||
if isPortrait {
|
||||
let itemSize = NSCollectionLayoutSize(
|
||||
widthDimension: .absolute(UIScreen.main.bounds.width - 2),
|
||||
widthDimension: .absolute(UIScreen.main.bounds.width - 32),
|
||||
heightDimension: .fractionalHeight(1)
|
||||
)
|
||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
|
@ -124,7 +124,7 @@ struct LiveTVChannelsView: View {
|
|||
)
|
||||
let groupSize = NSCollectionLayoutSize(
|
||||
widthDimension: .fractionalWidth(1.0),
|
||||
heightDimension: .absolute(132)
|
||||
heightDimension: .absolute(144)
|
||||
)
|
||||
let group = NSCollectionLayoutGroup.horizontal(
|
||||
layoutSize: groupSize,
|
||||
|
@ -149,7 +149,7 @@ struct LiveTVChannelsView: View {
|
|||
)
|
||||
let groupSize = NSCollectionLayoutSize(
|
||||
widthDimension: .fractionalWidth(1.0),
|
||||
heightDimension: .absolute(132)
|
||||
heightDimension: .absolute(144)
|
||||
)
|
||||
let group = NSCollectionLayoutGroup.horizontal(
|
||||
layoutSize: groupSize,
|
||||
|
|
|
@ -19,7 +19,11 @@ struct ExperimentalSettingsView: View {
|
|||
var nativePlayer
|
||||
@Default(.Experimental.liveTVAlphaEnabled)
|
||||
var liveTVAlphaEnabled
|
||||
|
||||
@Default(.Experimental.liveTVForceDirectPlay)
|
||||
var liveTVForceDirectPlay
|
||||
@Default(.Experimental.liveTVNativePlayer)
|
||||
var liveTVNativePlayer
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
|
@ -38,6 +42,10 @@ struct ExperimentalSettingsView: View {
|
|||
|
||||
Toggle("Live TV (Alpha)", isOn: $liveTVAlphaEnabled)
|
||||
|
||||
Toggle("Live TV Force Direct Play", isOn: $liveTVForceDirectPlay)
|
||||
|
||||
Toggle("Live TV Native Player", isOn: $liveTVNativePlayer)
|
||||
|
||||
} header: {
|
||||
Text("Live TV")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
//
|
||||
// Swiftfin is subject to the terms of the Mozilla Public
|
||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import AVKit
|
||||
import Combine
|
||||
import JellyfinAPI
|
||||
import UIKit
|
||||
|
||||
class LiveTVNativePlayerViewController: AVPlayerViewController {
|
||||
|
||||
let viewModel: VideoPlayerViewModel
|
||||
|
||||
var timeObserverToken: Any?
|
||||
|
||||
var lastProgressTicks: Int64 = 0
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(viewModel: VideoPlayerViewModel) {
|
||||
|
||||
self.viewModel = viewModel
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
let player: AVPlayer
|
||||
|
||||
if let transcodedStreamURL = viewModel.transcodedStreamURL {
|
||||
player = AVPlayer(url: transcodedStreamURL)
|
||||
} else {
|
||||
player = AVPlayer(url: viewModel.hlsStreamURL)
|
||||
}
|
||||
|
||||
player.appliesMediaSelectionCriteriaAutomatically = false
|
||||
|
||||
let timeScale = CMTimeScale(NSEC_PER_SEC)
|
||||
let time = CMTime(seconds: 5, preferredTimescale: timeScale)
|
||||
|
||||
timeObserverToken = player.addPeriodicTimeObserver(forInterval: time, queue: .main) { [weak self] time in
|
||||
if time.seconds != 0 {
|
||||
self?.sendProgressReport(seconds: time.seconds)
|
||||
}
|
||||
}
|
||||
|
||||
self.player = player
|
||||
|
||||
self.allowsPictureInPicturePlayback = true
|
||||
self.player?.allowsExternalPlayback = true
|
||||
}
|
||||
|
||||
private func createMetadataItem(for identifier: AVMetadataIdentifier,
|
||||
value: Any) -> AVMetadataItem
|
||||
{
|
||||
let item = AVMutableMetadataItem()
|
||||
item.identifier = identifier
|
||||
item.value = value as? NSCopying & NSObjectProtocol
|
||||
// Specify "und" to indicate an undefined language.
|
||||
item.extendedLanguageTag = "und"
|
||||
return item.copy() as! AVMetadataItem
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
stop()
|
||||
removePeriodicTimeObserver()
|
||||
}
|
||||
|
||||
func removePeriodicTimeObserver() {
|
||||
if let timeObserverToken = timeObserverToken {
|
||||
player?.removeTimeObserver(timeObserverToken)
|
||||
self.timeObserverToken = nil
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
player?.seek(to: CMTimeMake(value: viewModel.currentSecondTicks, timescale: 10_000_000),
|
||||
toleranceBefore: CMTimeMake(value: 1, timescale: 1), toleranceAfter: CMTimeMake(value: 1, timescale: 1),
|
||||
completionHandler: { _ in
|
||||
self.play()
|
||||
})
|
||||
}
|
||||
|
||||
private func play() {
|
||||
player?.play()
|
||||
|
||||
viewModel.sendPlayReport()
|
||||
}
|
||||
|
||||
private func sendProgressReport(seconds: Double) {
|
||||
viewModel.setSeconds(Int64(seconds))
|
||||
viewModel.sendProgressReport()
|
||||
}
|
||||
|
||||
private func stop() {
|
||||
self.player?.pause()
|
||||
viewModel.sendStopReport()
|
||||
}
|
||||
}
|
|
@ -9,19 +9,19 @@
|
|||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
//struct NativePlayerView: UIViewControllerRepresentable {
|
||||
//
|
||||
// let viewModel: VideoPlayerViewModel
|
||||
//
|
||||
// typealias UIViewControllerType = NativePlayerViewController
|
||||
//
|
||||
// func makeUIViewController(context: Context) -> NativePlayerViewController {
|
||||
//
|
||||
// NativePlayerViewController(viewModel: viewModel)
|
||||
// }
|
||||
//
|
||||
// func updateUIViewController(_ uiViewController: NativePlayerViewController, context: Context) {}
|
||||
//}
|
||||
struct LiveTVNativePlayerView: UIViewControllerRepresentable {
|
||||
|
||||
let viewModel: VideoPlayerViewModel
|
||||
|
||||
typealias UIViewControllerType = LiveTVNativePlayerViewController
|
||||
|
||||
func makeUIViewController(context: Context) -> LiveTVNativePlayerViewController {
|
||||
|
||||
LiveTVNativePlayerViewController(viewModel: viewModel)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: LiveTVNativePlayerViewController, context: Context) {}
|
||||
}
|
||||
|
||||
struct LiveTVPlayerView: UIViewControllerRepresentable {
|
||||
|
||||
|
|
Loading…
Reference in New Issue