ios chapters
This commit is contained in:
parent
13f457f52a
commit
fa01de49a6
|
@ -112,6 +112,7 @@ extension BaseItemDto {
|
||||||
response: response,
|
response: response,
|
||||||
audioStreams: audioStreams,
|
audioStreams: audioStreams,
|
||||||
subtitleStreams: subtitleStreams,
|
subtitleStreams: subtitleStreams,
|
||||||
|
chapters: modifiedSelfItem.chapters ?? [],
|
||||||
selectedAudioStreamIndex: defaultAudioStream?.index ?? -1,
|
selectedAudioStreamIndex: defaultAudioStream?.index ?? -1,
|
||||||
selectedSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1,
|
selectedSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1,
|
||||||
subtitlesEnabled: subtitlesEnabled,
|
subtitlesEnabled: subtitlesEnabled,
|
||||||
|
|
|
@ -312,4 +312,22 @@ public extension BaseItemDto {
|
||||||
dateFormatter.dateStyle = .medium
|
dateFormatter.dateStyle = .medium
|
||||||
return dateFormatter.string(from: premiereDate)
|
return dateFormatter.string(from: premiereDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Chapter Images
|
||||||
|
|
||||||
|
func getChapterImage(maxWidth: Int) -> [URL] {
|
||||||
|
guard let chapters = chapters, !chapters.isEmpty else { return [] }
|
||||||
|
|
||||||
|
var chapterImageURLs: [URL] = []
|
||||||
|
|
||||||
|
for chapterIndex in 0 ..< chapters.count {
|
||||||
|
let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: id ?? "",
|
||||||
|
imageType: .chapter,
|
||||||
|
maxWidth: maxWidth,
|
||||||
|
imageIndex: chapterIndex).URLString
|
||||||
|
chapterImageURLs.append(URL(string: urlString)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapterImageURLs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// 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 Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
extension ChapterInfo {
|
||||||
|
|
||||||
|
var timestampLabel: String {
|
||||||
|
let seconds = (startPositionTicks ?? 0) / 10_000_000
|
||||||
|
return seconds.toReadableString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Int64 {
|
||||||
|
|
||||||
|
func toReadableString() -> String {
|
||||||
|
|
||||||
|
let s = Int(self) % 60
|
||||||
|
let mn = (Int(self) / 60) % 60
|
||||||
|
let hr = (Int(self) / 3600)
|
||||||
|
|
||||||
|
var final = ""
|
||||||
|
|
||||||
|
if hr != 0 {
|
||||||
|
final += "\(hr):"
|
||||||
|
}
|
||||||
|
|
||||||
|
if mn != 0 {
|
||||||
|
final += String(format: "%0.2d:", mn)
|
||||||
|
} else {
|
||||||
|
final += "00:"
|
||||||
|
}
|
||||||
|
|
||||||
|
final += String(format: "%0.2d", s)
|
||||||
|
|
||||||
|
return final
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,6 +50,8 @@ internal enum L10n {
|
||||||
internal static let changeServer = L10n.tr("Localizable", "changeServer")
|
internal static let changeServer = L10n.tr("Localizable", "changeServer")
|
||||||
/// Channels
|
/// Channels
|
||||||
internal static let channels = L10n.tr("Localizable", "channels")
|
internal static let channels = L10n.tr("Localizable", "channels")
|
||||||
|
/// Chapters
|
||||||
|
internal static let chapters = L10n.tr("Localizable", "chapters")
|
||||||
/// Cinematic Views
|
/// Cinematic Views
|
||||||
internal static let cinematicViews = L10n.tr("Localizable", "cinematicViews")
|
internal static let cinematicViews = L10n.tr("Localizable", "cinematicViews")
|
||||||
/// Closed Captions
|
/// Closed Captions
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Algorithms
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -110,6 +111,7 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
let transcodedStreamURL: URL?
|
let transcodedStreamURL: URL?
|
||||||
let audioStreams: [MediaStream]
|
let audioStreams: [MediaStream]
|
||||||
let subtitleStreams: [MediaStream]
|
let subtitleStreams: [MediaStream]
|
||||||
|
let chapters: [ChapterInfo]
|
||||||
let overlayType: OverlayType
|
let overlayType: OverlayType
|
||||||
let jumpGesturesEnabled: Bool
|
let jumpGesturesEnabled: Bool
|
||||||
let resumeOffset: Bool
|
let resumeOffset: Bool
|
||||||
|
@ -155,6 +157,22 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
subtitleStreams.first(where: { $0.index == selectedSubtitleStreamIndex })
|
subtitleStreams.first(where: { $0.index == selectedSubtitleStreamIndex })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var currentChapter: ChapterInfo? {
|
||||||
|
|
||||||
|
let chapterPairs = chapters.adjacentPairs().map { ($0, $1) }
|
||||||
|
let chapterRanges = chapterPairs.map { ($0.startPositionTicks ?? 0, ($1.startPositionTicks ?? 1) - 1) }
|
||||||
|
|
||||||
|
for chapterRangeIndex in 0 ..< chapterRanges.count {
|
||||||
|
if chapterRanges[chapterRangeIndex].0 <= currentSecondTicks &&
|
||||||
|
currentSecondTicks < chapterRanges[chapterRangeIndex].1
|
||||||
|
{
|
||||||
|
return chapterPairs[chapterRangeIndex].0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Necessary PassthroughSubject to capture manual scrubbing from sliders
|
// Necessary PassthroughSubject to capture manual scrubbing from sliders
|
||||||
let sliderScrubbingSubject = PassthroughSubject<VideoPlayerViewModel, Never>()
|
let sliderScrubbingSubject = PassthroughSubject<VideoPlayerViewModel, Never>()
|
||||||
|
|
||||||
|
@ -174,6 +192,7 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
response: PlaybackInfoResponse,
|
response: PlaybackInfoResponse,
|
||||||
audioStreams: [MediaStream],
|
audioStreams: [MediaStream],
|
||||||
subtitleStreams: [MediaStream],
|
subtitleStreams: [MediaStream],
|
||||||
|
chapters: [ChapterInfo],
|
||||||
selectedAudioStreamIndex: Int,
|
selectedAudioStreamIndex: Int,
|
||||||
selectedSubtitleStreamIndex: Int,
|
selectedSubtitleStreamIndex: Int,
|
||||||
subtitlesEnabled: Bool,
|
subtitlesEnabled: Bool,
|
||||||
|
@ -195,6 +214,7 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
self.response = response
|
self.response = response
|
||||||
self.audioStreams = audioStreams
|
self.audioStreams = audioStreams
|
||||||
self.subtitleStreams = subtitleStreams
|
self.subtitleStreams = subtitleStreams
|
||||||
|
self.chapters = chapters
|
||||||
self.selectedAudioStreamIndex = selectedAudioStreamIndex
|
self.selectedAudioStreamIndex = selectedAudioStreamIndex
|
||||||
self.selectedSubtitleStreamIndex = selectedSubtitleStreamIndex
|
self.selectedSubtitleStreamIndex = selectedSubtitleStreamIndex
|
||||||
self.subtitlesEnabled = subtitlesEnabled
|
self.subtitlesEnabled = subtitlesEnabled
|
||||||
|
|
|
@ -239,6 +239,10 @@
|
||||||
C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; };
|
C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; };
|
||||||
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; };
|
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; };
|
||||||
C4E52305272CE68800654268 /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; };
|
C4E52305272CE68800654268 /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; };
|
||||||
|
E1002B5F2793C3BE00E47059 /* VLCPlayerChapterOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1002B5E2793C3BE00E47059 /* VLCPlayerChapterOverlayView.swift */; };
|
||||||
|
E1002B642793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1002B632793CEE700E47059 /* ChapterInfoExtensions.swift */; };
|
||||||
|
E1002B652793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1002B632793CEE700E47059 /* ChapterInfoExtensions.swift */; };
|
||||||
|
E1002B682793CFBA00E47059 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = E1002B672793CFBA00E47059 /* Algorithms */; };
|
||||||
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; };
|
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; };
|
||||||
E103A6A0278A7E4500820EC7 /* UICinematicBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A69F278A7E4500820EC7 /* UICinematicBackgroundView.swift */; };
|
E103A6A0278A7E4500820EC7 /* UICinematicBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A69F278A7E4500820EC7 /* UICinematicBackgroundView.swift */; };
|
||||||
E103A6A3278A7EC400820EC7 /* CinematicBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A2278A7EC400820EC7 /* CinematicBackgroundView.swift */; };
|
E103A6A3278A7EC400820EC7 /* CinematicBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A2278A7EC400820EC7 /* CinematicBackgroundView.swift */; };
|
||||||
|
@ -648,6 +652,8 @@
|
||||||
C4E508172703E8190045C9AB /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = "<group>"; };
|
C4E508172703E8190045C9AB /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = "<group>"; };
|
||||||
C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = "<group>"; };
|
C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = "<group>"; };
|
||||||
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemElement.swift; sourceTree = "<group>"; };
|
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemElement.swift; sourceTree = "<group>"; };
|
||||||
|
E1002B5E2793C3BE00E47059 /* VLCPlayerChapterOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayerChapterOverlayView.swift; sourceTree = "<group>"; };
|
||||||
|
E1002B632793CEE700E47059 /* ChapterInfoExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterInfoExtensions.swift; sourceTree = "<group>"; };
|
||||||
E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = "<group>"; };
|
E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = "<group>"; };
|
||||||
E103A69F278A7E4500820EC7 /* UICinematicBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICinematicBackgroundView.swift; sourceTree = "<group>"; };
|
E103A69F278A7E4500820EC7 /* UICinematicBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICinematicBackgroundView.swift; sourceTree = "<group>"; };
|
||||||
E103A6A2278A7EC400820EC7 /* CinematicBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicBackgroundView.swift; sourceTree = "<group>"; };
|
E103A6A2278A7EC400820EC7 /* CinematicBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicBackgroundView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -798,6 +804,7 @@
|
||||||
E13DD3D327168E65009D4DAF /* Defaults in Frameworks */,
|
E13DD3D327168E65009D4DAF /* Defaults in Frameworks */,
|
||||||
E1361DA7278FA7A300BEC523 /* NukeUI in Frameworks */,
|
E1361DA7278FA7A300BEC523 /* NukeUI in Frameworks */,
|
||||||
53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */,
|
53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */,
|
||||||
|
E1002B682793CFBA00E47059 /* Algorithms in Frameworks */,
|
||||||
E10EAA4D277BB716000269ED /* Sliders in Frameworks */,
|
E10EAA4D277BB716000269ED /* Sliders in Frameworks */,
|
||||||
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
|
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
|
||||||
E1A99999271A3429008E78C0 /* SwiftUICollection in Frameworks */,
|
E1A99999271A3429008E78C0 /* SwiftUICollection in Frameworks */,
|
||||||
|
@ -845,7 +852,7 @@
|
||||||
children = (
|
children = (
|
||||||
E1C812C6277AE40900918266 /* PlayerOverlayDelegate.swift */,
|
E1C812C6277AE40900918266 /* PlayerOverlayDelegate.swift */,
|
||||||
E178859C2780F5300094FBCF /* tvOSSLider */,
|
E178859C2780F5300094FBCF /* tvOSSLider */,
|
||||||
E17885A7278130690094FBCF /* tvOSOverlay */,
|
E17885A7278130690094FBCF /* Overlays */,
|
||||||
E1C812C8277AE40900918266 /* VideoPlayerView.swift */,
|
E1C812C8277AE40900918266 /* VideoPlayerView.swift */,
|
||||||
E1384943278036C70024FB48 /* VLCPlayerViewController.swift */,
|
E1384943278036C70024FB48 /* VLCPlayerViewController.swift */,
|
||||||
);
|
);
|
||||||
|
@ -1343,6 +1350,15 @@
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E1002B692793E12E00E47059 /* Overlays */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E1C812BB277A8E5D00918266 /* VLCPlayerOverlayView.swift */,
|
||||||
|
E1002B5E2793C3BE00E47059 /* VLCPlayerChapterOverlayView.swift */,
|
||||||
|
);
|
||||||
|
path = Overlays;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E103A6A1278A7EB500820EC7 /* HomeCinematicView */ = {
|
E103A6A1278A7EB500820EC7 /* HomeCinematicView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1500,14 +1516,14 @@
|
||||||
path = tvOSSLider;
|
path = tvOSSLider;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E17885A7278130690094FBCF /* tvOSOverlay */ = {
|
E17885A7278130690094FBCF /* Overlays */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E1E5D552278419D900692DFE /* ConfirmCloseOverlay.swift */,
|
E1E5D552278419D900692DFE /* ConfirmCloseOverlay.swift */,
|
||||||
E1FA2F7327818A8800B4C270 /* SmallMenuOverlay.swift */,
|
E1FA2F7327818A8800B4C270 /* SmallMenuOverlay.swift */,
|
||||||
E178859F2780F55C0094FBCF /* tvOSVLCOverlay.swift */,
|
E178859F2780F55C0094FBCF /* tvOSVLCOverlay.swift */,
|
||||||
);
|
);
|
||||||
path = tvOSOverlay;
|
path = Overlays;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E18845FA26DEACBE00B0C5B7 /* Portrait */ = {
|
E18845FA26DEACBE00B0C5B7 /* Portrait */ = {
|
||||||
|
@ -1543,7 +1559,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E1C812B5277A8E5D00918266 /* PlayerOverlayDelegate.swift */,
|
E1C812B5277A8E5D00918266 /* PlayerOverlayDelegate.swift */,
|
||||||
E1C812BB277A8E5D00918266 /* VLCPlayerOverlayView.swift */,
|
E1002B692793E12E00E47059 /* Overlays */,
|
||||||
E1C812B8277A8E5D00918266 /* VLCPlayerView.swift */,
|
E1C812B8277A8E5D00918266 /* VLCPlayerView.swift */,
|
||||||
E1C812B6277A8E5D00918266 /* VLCPlayerViewController.swift */,
|
E1C812B6277A8E5D00918266 /* VLCPlayerViewController.swift */,
|
||||||
);
|
);
|
||||||
|
@ -1567,6 +1583,7 @@
|
||||||
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */,
|
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */,
|
||||||
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */,
|
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */,
|
||||||
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */,
|
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */,
|
||||||
|
E1002B632793CEE700E47059 /* ChapterInfoExtensions.swift */,
|
||||||
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */,
|
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */,
|
||||||
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */,
|
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */,
|
||||||
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */,
|
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */,
|
||||||
|
@ -1754,6 +1771,7 @@
|
||||||
E10EAA4C277BB716000269ED /* Sliders */,
|
E10EAA4C277BB716000269ED /* Sliders */,
|
||||||
E1AE8E7B2789135A00FBDDAA /* Nuke */,
|
E1AE8E7B2789135A00FBDDAA /* Nuke */,
|
||||||
E1361DA6278FA7A300BEC523 /* NukeUI */,
|
E1361DA6278FA7A300BEC523 /* NukeUI */,
|
||||||
|
E1002B672793CFBA00E47059 /* Algorithms */,
|
||||||
);
|
);
|
||||||
productName = JellyfinPlayer;
|
productName = JellyfinPlayer;
|
||||||
productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */;
|
productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */;
|
||||||
|
@ -1847,14 +1865,15 @@
|
||||||
E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */,
|
E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */,
|
||||||
E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */,
|
E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */,
|
||||||
E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */,
|
E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */,
|
||||||
|
E1002B662793CFBA00E47059 /* XCRemoteSwiftPackageReference "swift-algorithms" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
5377CBF0263B596A003A4E83 /* Swiftfin iOS */,
|
5377CBF0263B596A003A4E83 /* Swiftfin iOS */,
|
||||||
5358705F2669D21600D05A09 /* Swiftfin tvOS */,
|
|
||||||
628B951F2670CABD0091AF3B /* Swiftfin Widget */,
|
628B951F2670CABD0091AF3B /* Swiftfin Widget */,
|
||||||
|
5358705F2669D21600D05A09 /* Swiftfin tvOS */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
@ -2138,6 +2157,7 @@
|
||||||
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */,
|
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */,
|
||||||
536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */,
|
536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */,
|
||||||
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */,
|
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */,
|
||||||
|
E1002B652793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
||||||
091B5A8E268315D400D78B61 /* UDPBroadCastConnection.swift in Sources */,
|
091B5A8E268315D400D78B61 /* UDPBroadCastConnection.swift in Sources */,
|
||||||
E103A6A5278A82E500820EC7 /* HomeCinematicView.swift in Sources */,
|
E103A6A5278A82E500820EC7 /* HomeCinematicView.swift in Sources */,
|
||||||
E10EAA50277BBCC4000269ED /* CGSizeExtensions.swift in Sources */,
|
E10EAA50277BBCC4000269ED /* CGSizeExtensions.swift in Sources */,
|
||||||
|
@ -2339,6 +2359,7 @@
|
||||||
E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */,
|
E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */,
|
||||||
E10D87DA2784E4F100BD264C /* ItemViewDetailsView.swift in Sources */,
|
E10D87DA2784E4F100BD264C /* ItemViewDetailsView.swift in Sources */,
|
||||||
E1C812C3277A8E5D00918266 /* VLCPlayerOverlayView.swift in Sources */,
|
E1C812C3277A8E5D00918266 /* VLCPlayerOverlayView.swift in Sources */,
|
||||||
|
E1002B642793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
||||||
E188460026DECB9E00B0C5B7 /* ItemLandscapeTopBarView.swift in Sources */,
|
E188460026DECB9E00B0C5B7 /* ItemLandscapeTopBarView.swift in Sources */,
|
||||||
091B5A8B2683142E00D78B61 /* UDPBroadCastConnection.swift in Sources */,
|
091B5A8B2683142E00D78B61 /* UDPBroadCastConnection.swift in Sources */,
|
||||||
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
||||||
|
@ -2358,6 +2379,7 @@
|
||||||
E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */,
|
E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */,
|
||||||
E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */,
|
E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */,
|
||||||
E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */,
|
E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */,
|
||||||
|
E1002B5F2793C3BE00E47059 /* VLCPlayerChapterOverlayView.swift in Sources */,
|
||||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
||||||
6220D0C626D62D8700B8E046 /* iOSVideoPlayerCoordinator.swift in Sources */,
|
6220D0C626D62D8700B8E046 /* iOSVideoPlayerCoordinator.swift in Sources */,
|
||||||
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
|
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
|
||||||
|
@ -2792,7 +2814,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 66;
|
CURRENT_PROJECT_VERSION = 66;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = TY84JMYEFE;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
EXCLUDED_ARCHS = "";
|
EXCLUDED_ARCHS = "";
|
||||||
|
@ -2829,7 +2851,7 @@
|
||||||
CURRENT_PROJECT_VERSION = 66;
|
CURRENT_PROJECT_VERSION = 66;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = TY84JMYEFE;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
EXCLUDED_ARCHS = "";
|
EXCLUDED_ARCHS = "";
|
||||||
|
@ -2860,7 +2882,7 @@
|
||||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 66;
|
CURRENT_PROJECT_VERSION = 66;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = TY84JMYEFE;
|
||||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -2887,7 +2909,7 @@
|
||||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 66;
|
CURRENT_PROJECT_VERSION = 66;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = TY84JMYEFE;
|
||||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -2995,6 +3017,14 @@
|
||||||
kind = branch;
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
E1002B662793CFBA00E47059 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/apple/swift-algorithms.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 1.0.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */ = {
|
E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/jellyfin/jellyfin-sdk-swift";
|
repositoryURL = "https://github.com/jellyfin/jellyfin-sdk-swift";
|
||||||
|
@ -3117,6 +3147,11 @@
|
||||||
package = 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */;
|
package = 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */;
|
||||||
productName = Stinsen;
|
productName = Stinsen;
|
||||||
};
|
};
|
||||||
|
E1002B672793CFBA00E47059 /* Algorithms */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = E1002B662793CFBA00E47059 /* XCRemoteSwiftPackageReference "swift-algorithms" */;
|
||||||
|
productName = Algorithms;
|
||||||
|
};
|
||||||
E10EAA44277BB646000269ED /* JellyfinAPI */ = {
|
E10EAA44277BB646000269ED /* JellyfinAPI */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
|
package = E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
|
||||||
|
|
|
@ -109,6 +109,15 @@
|
||||||
"version": "2.0.3"
|
"version": "2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-algorithms",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-algorithms.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "b14b7f4c528c942f121c8b860b9410b2bf57825e",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"package": "swift-log",
|
"package": "swift-log",
|
||||||
"repositoryURL": "https://github.com/apple/swift-log.git",
|
"repositoryURL": "https://github.com/apple/swift-log.git",
|
||||||
|
@ -118,6 +127,15 @@
|
||||||
"version": "1.4.2"
|
"version": "1.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-numerics",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-numerics",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "0a5bc04095a675662cf24757cc0640aa2204253b",
|
||||||
|
"version": "1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"package": "Introspect",
|
"package": "Introspect",
|
||||||
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect",
|
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect",
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
//
|
||||||
|
// 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 JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct VLCPlayerChapterOverlayView: View {
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: VideoPlayerViewModel
|
||||||
|
private let chapterImages: [URL]
|
||||||
|
|
||||||
|
init(viewModel: VideoPlayerViewModel) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
self.chapterImages = viewModel.item.getChapterImage(maxWidth: 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var mainBody: some View {
|
||||||
|
ZStack(alignment: .bottom) {
|
||||||
|
|
||||||
|
LinearGradient(gradient: Gradient(colors: [.clear, .black.opacity(0.8), .black]),
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.frame(height: 300)
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
|
||||||
|
L10n.chapters.text
|
||||||
|
.font(.title3)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
ScrollViewReader { reader in
|
||||||
|
HStack {
|
||||||
|
ForEach(0 ..< viewModel.chapters.count) { chapterIndex in
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Button {
|
||||||
|
viewModel.playerOverlayDelegate?.didSelectChapter(viewModel.chapters[chapterIndex])
|
||||||
|
} label: {
|
||||||
|
ImageView(src: chapterImages[chapterIndex])
|
||||||
|
.cornerRadius(10)
|
||||||
|
.frame(width: 150, height: 100)
|
||||||
|
.overlay {
|
||||||
|
if viewModel.chapters[chapterIndex] == viewModel.currentChapter {
|
||||||
|
RoundedRectangle(cornerRadius: 6)
|
||||||
|
.stroke(Color.jellyfinPurple, lineWidth: 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
|
||||||
|
Text(viewModel.chapters[chapterIndex].name ?? L10n.noTitle)
|
||||||
|
.font(.subheadline)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Text(viewModel.chapters[chapterIndex].timestampLabel)
|
||||||
|
.font(.subheadline)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(Color(UIColor.systemBlue))
|
||||||
|
.padding(.vertical, 2)
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
.background {
|
||||||
|
Color(UIColor.darkGray).opacity(0.2).cornerRadius(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.id(viewModel.chapters[chapterIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top)
|
||||||
|
.onAppear {
|
||||||
|
reader.scrollTo(viewModel.currentChapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
mainBody
|
||||||
|
.edgesIgnoringSafeArea(.bottom)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
viewModel.playerOverlayDelegate?.didSelectChapters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,7 +76,8 @@ struct VLCPlayerOverlayView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(viewModel.title)
|
Text(viewModel.title)
|
||||||
.font(.system(size: 28, weight: .regular, design: .default))
|
.font(.title3)
|
||||||
|
.fontWeight(.bold)
|
||||||
.alignmentGuide(.EpisodeSeriesAlignmentGuide) { context in
|
.alignmentGuide(.EpisodeSeriesAlignmentGuide) { context in
|
||||||
context[.leading]
|
context[.leading]
|
||||||
}
|
}
|
||||||
|
@ -193,6 +194,17 @@ struct VLCPlayerOverlayView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !viewModel.chapters.isEmpty {
|
||||||
|
Button {
|
||||||
|
viewModel.playerOverlayDelegate?.didSelectChapters()
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "list.dash")
|
||||||
|
L10n.chapters.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if viewModel.shouldShowJumpButtonsInOverlayMenu {
|
if viewModel.shouldShowJumpButtonsInOverlayMenu {
|
||||||
Menu {
|
Menu {
|
||||||
ForEach(VideoPlayerJumpLength.allCases, id: \.self) { forwardLength in
|
ForEach(VideoPlayerJumpLength.allCases, id: \.self) { forwardLength in
|
||||||
|
@ -247,7 +259,7 @@ struct VLCPlayerOverlayView: View {
|
||||||
.alignmentGuide(.EpisodeSeriesAlignmentGuide) { context in
|
.alignmentGuide(.EpisodeSeriesAlignmentGuide) { context in
|
||||||
context[.leading]
|
context[.leading]
|
||||||
}
|
}
|
||||||
.offset(y: -10)
|
.offset(y: -20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,6 +401,7 @@ struct VLCPlayerCompactOverlayView_Previews: PreviewProvider {
|
||||||
response: PlaybackInfoResponse(),
|
response: PlaybackInfoResponse(),
|
||||||
audioStreams: [MediaStream(displayTitle: "English", index: -1)],
|
audioStreams: [MediaStream(displayTitle: "English", index: -1)],
|
||||||
subtitleStreams: [MediaStream(displayTitle: "None", index: -1)],
|
subtitleStreams: [MediaStream(displayTitle: "None", index: -1)],
|
||||||
|
chapters: [],
|
||||||
selectedAudioStreamIndex: -1,
|
selectedAudioStreamIndex: -1,
|
||||||
selectedSubtitleStreamIndex: -1,
|
selectedSubtitleStreamIndex: -1,
|
||||||
subtitlesEnabled: true,
|
subtitlesEnabled: true,
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
protocol PlayerOverlayDelegate {
|
protocol PlayerOverlayDelegate {
|
||||||
|
|
||||||
|
@ -28,4 +29,7 @@ protocol PlayerOverlayDelegate {
|
||||||
|
|
||||||
func didSelectPlayPreviousItem()
|
func didSelectPlayPreviousItem()
|
||||||
func didSelectPlayNextItem()
|
func didSelectPlayNextItem()
|
||||||
|
|
||||||
|
func didSelectChapters()
|
||||||
|
func didSelectChapter(_ chapter: ChapterInfo)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,14 @@ class VLCPlayerViewController: UIViewController {
|
||||||
currentOverlayHostingController?.view.alpha ?? 0 > 0
|
currentOverlayHostingController?.view.alpha ?? 0 > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var displayingChapterOverlay: Bool {
|
||||||
|
currentChapterOverlayHostingController?.view.alpha ?? 0 > 0
|
||||||
|
}
|
||||||
|
|
||||||
private lazy var videoContentView = makeVideoContentView()
|
private lazy var videoContentView = makeVideoContentView()
|
||||||
private lazy var mainGestureView = makeTapGestureView()
|
private lazy var mainGestureView = makeTapGestureView()
|
||||||
private var currentOverlayHostingController: UIHostingController<VLCPlayerOverlayView>?
|
private var currentOverlayHostingController: UIHostingController<VLCPlayerOverlayView>?
|
||||||
|
private var currentChapterOverlayHostingController: UIHostingController<VLCPlayerChapterOverlayView>?
|
||||||
private var currentJumpBackwardOverlayView: UIImageView?
|
private var currentJumpBackwardOverlayView: UIImageView?
|
||||||
private var currentJumpForwardOverlayView: UIImageView?
|
private var currentJumpForwardOverlayView: UIImageView?
|
||||||
|
|
||||||
|
@ -119,6 +124,8 @@ class VLCPlayerViewController: UIViewController {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
private func appWillResignActive() {
|
private func appWillResignActive() {
|
||||||
|
hideChaptersOverlay()
|
||||||
|
|
||||||
showOverlay()
|
showOverlay()
|
||||||
|
|
||||||
stopOverlayDismissTimer()
|
stopOverlayDismissTimer()
|
||||||
|
@ -225,6 +232,38 @@ class VLCPlayerViewController: UIViewController {
|
||||||
|
|
||||||
self.currentOverlayHostingController = newOverlayHostingController
|
self.currentOverlayHostingController = newOverlayHostingController
|
||||||
|
|
||||||
|
if let currentChapterOverlayHostingController = currentChapterOverlayHostingController {
|
||||||
|
UIView.animate(withDuration: 0.5) {
|
||||||
|
currentChapterOverlayHostingController.view.alpha = 0
|
||||||
|
} completion: { _ in
|
||||||
|
currentChapterOverlayHostingController.view.isHidden = true
|
||||||
|
|
||||||
|
currentChapterOverlayHostingController.view.removeFromSuperview()
|
||||||
|
currentChapterOverlayHostingController.removeFromParent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let newChapterOverlayView = VLCPlayerChapterOverlayView(viewModel: viewModel)
|
||||||
|
let newChapterOverlayHostingController = UIHostingController(rootView: newChapterOverlayView)
|
||||||
|
|
||||||
|
newChapterOverlayHostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
newChapterOverlayHostingController.view.backgroundColor = UIColor.clear
|
||||||
|
|
||||||
|
newChapterOverlayHostingController.view.alpha = 0
|
||||||
|
|
||||||
|
addChild(newChapterOverlayHostingController)
|
||||||
|
view.addSubview(newChapterOverlayHostingController.view)
|
||||||
|
newChapterOverlayHostingController.didMove(toParent: self)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
newChapterOverlayHostingController.view.topAnchor.constraint(equalTo: videoContentView.topAnchor),
|
||||||
|
newChapterOverlayHostingController.view.bottomAnchor.constraint(equalTo: videoContentView.bottomAnchor),
|
||||||
|
newChapterOverlayHostingController.view.leftAnchor.constraint(equalTo: videoContentView.leftAnchor),
|
||||||
|
newChapterOverlayHostingController.view.rightAnchor.constraint(equalTo: videoContentView.rightAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
self.currentChapterOverlayHostingController = newChapterOverlayHostingController
|
||||||
|
|
||||||
// There is a weird behavior when after setting the new overlays that the navigation bar pops up, re-hide it
|
// There is a weird behavior when after setting the new overlays that the navigation bar pops up, re-hide it
|
||||||
self.navigationController?.isNavigationBarHidden = true
|
self.navigationController?.isNavigationBarHidden = true
|
||||||
}
|
}
|
||||||
|
@ -514,6 +553,31 @@ extension VLCPlayerViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Hide/Show Chapters
|
||||||
|
|
||||||
|
extension VLCPlayerViewController {
|
||||||
|
|
||||||
|
private func showChaptersOverlay() {
|
||||||
|
guard let overlayHostingController = currentChapterOverlayHostingController else { return }
|
||||||
|
|
||||||
|
guard overlayHostingController.view.alpha != 1 else { return }
|
||||||
|
|
||||||
|
UIView.animate(withDuration: 0.2) {
|
||||||
|
overlayHostingController.view.alpha = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hideChaptersOverlay() {
|
||||||
|
guard let overlayHostingController = currentChapterOverlayHostingController else { return }
|
||||||
|
|
||||||
|
guard overlayHostingController.view.alpha != 0 else { return }
|
||||||
|
|
||||||
|
UIView.animate(withDuration: 0.2) {
|
||||||
|
overlayHostingController.view.alpha = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: OverlayTimer
|
// MARK: OverlayTimer
|
||||||
|
|
||||||
extension VLCPlayerViewController {
|
extension VLCPlayerViewController {
|
||||||
|
@ -724,4 +788,27 @@ extension VLCPlayerViewController: PlayerOverlayDelegate {
|
||||||
startPlayback()
|
startPlayback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didSelectChapters() {
|
||||||
|
if displayingChapterOverlay {
|
||||||
|
hideChaptersOverlay()
|
||||||
|
} else {
|
||||||
|
hideOverlay()
|
||||||
|
showChaptersOverlay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func didSelectChapter(_ chapter: ChapterInfo) {
|
||||||
|
let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000)
|
||||||
|
let chapterSeconds = Double((chapter.startPositionTicks ?? 0) / 10_000_000)
|
||||||
|
let newPositionOffset = chapterSeconds - videoPosition
|
||||||
|
|
||||||
|
if newPositionOffset > 0 {
|
||||||
|
vlcMediaPlayer.jumpForward(Int32(newPositionOffset))
|
||||||
|
} else {
|
||||||
|
vlcMediaPlayer.jumpBackward(Int32(abs(newPositionOffset)))
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.sendProgressReport()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue