release 49
This commit is contained in:
parent
571e0c4e8e
commit
a7ed08e2b5
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>43</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchScreen</key>
|
||||
|
|
|
@ -1002,7 +1002,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "JellyfinPlayer tvOS/JellyfinPlayer tvOS.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1030,7 +1030,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "JellyfinPlayer tvOS/JellyfinPlayer tvOS.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1179,7 +1179,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
@ -1213,7 +1213,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||
|
@ -1245,7 +1245,7 @@
|
|||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
|
@ -1270,7 +1270,7 @@
|
|||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
|
|
|
@ -80,8 +80,6 @@ struct ContinueWatchingView: View {
|
|||
Spacer().frame(width: 2)
|
||||
}.frame(height: 215)
|
||||
.padding(.bottom, 10)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,14 @@ import SwiftUI
|
|||
|
||||
struct HomeView: View {
|
||||
@StateObject var viewModel = HomeViewModel()
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
@Environment(\.horizontalSizeClass) var hSizeClass
|
||||
@Environment(\.verticalSizeClass) var vSizeClass
|
||||
@State var showingSettings = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if(viewModel.isLoading) {
|
||||
ProgressView()
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
Spacer().frame(height: hSizeClass == .compact && vSizeClass == .regular ? 0 : 16)
|
||||
|
@ -55,25 +56,19 @@ struct HomeView: View {
|
|||
Spacer().frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
|
||||
}
|
||||
}
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
.onRotate {
|
||||
orientation = $0
|
||||
}
|
||||
.navigationTitle(MainTabView.Tab.home.localized)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showingSettings = true
|
||||
} label: {
|
||||
Image(systemName: "gear")
|
||||
.navigationTitle(MainTabView.Tab.home.localized)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showingSettings = true
|
||||
} label: {
|
||||
Image(systemName: "gear")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showingSettings) {
|
||||
SettingsView(viewModel: SettingsViewModel(), close: $showingSettings)
|
||||
.fullScreenCover(isPresented: $showingSettings) {
|
||||
SettingsView(viewModel: SettingsViewModel(), close: $showingSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,24 +19,26 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>43</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>${PRODUCT_NAME} uses Bluetooth to discover nearby Cast devices.</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSAllowsArbitraryLoadsForMedia</key>
|
||||
<true/>
|
||||
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
||||
<true/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>${PRODUCT_NAME} uses Bluetooth to discover nearby Cast devices.</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>${PRODUCT_NAME} uses Bluetooth to discover nearby Cast devices.</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_googlecast._tcp</string>
|
||||
|
@ -45,6 +47,8 @@
|
|||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>${PRODUCT_NAME} uses the local network to connect to your Jellyfin server & discover Cast-enabled devices on your WiFi
|
||||
network.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>${PRODUCT_NAME} uses microphone access to listen for ultrasonic tokens when pairing with nearby Cast devices.</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
|
|
|
@ -27,31 +27,32 @@ struct LibraryFilterView: View {
|
|||
MultiSelector(label: "Genres",
|
||||
options: viewModel.possibleGenres,
|
||||
optionToString: { $0.name ?? "" },
|
||||
selected: $viewModel.modifyedFilters.withGenres)
|
||||
selected: $viewModel.modifiedFilters.withGenres)
|
||||
}
|
||||
if viewModel.enabledFilterType.contains(.filter) {
|
||||
MultiSelector(label: "Filters",
|
||||
options: viewModel.possibleItemFilters,
|
||||
optionToString: { $0.localized },
|
||||
selected: $viewModel.modifyedFilters.filters)
|
||||
selected: $viewModel.modifiedFilters.filters)
|
||||
}
|
||||
if viewModel.enabledFilterType.contains(.tag) {
|
||||
MultiSelector(label: "Tags",
|
||||
options: viewModel.possibleTags,
|
||||
optionToString: { $0 },
|
||||
selected: $viewModel.modifyedFilters.tags)
|
||||
selected: $viewModel.modifiedFilters.tags)
|
||||
}
|
||||
if viewModel.enabledFilterType.contains(.sortBy) {
|
||||
MultiSelector(label: "Sort by",
|
||||
options: viewModel.possibleSortBys,
|
||||
optionToString: { $0.localized },
|
||||
selected: $viewModel.modifyedFilters.sortBy)
|
||||
selected: $viewModel.modifiedFilters.sortBy)
|
||||
}
|
||||
if viewModel.enabledFilterType.contains(.sortOrder) {
|
||||
MultiSelector(label: "Sort Order",
|
||||
options: viewModel.possibleSortOrders,
|
||||
optionToString: { $0.localized },
|
||||
selected: $viewModel.modifyedFilters.sortOrder)
|
||||
Picker(selection: $viewModel.modifiedFilters.sortOrder, label: Text("Order")) {
|
||||
ForEach(viewModel.possibleSortOrders, id: \.self) { so in
|
||||
Text("\(so.rawValue)").tag(so.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if viewModel.isLoading {
|
||||
|
@ -69,7 +70,7 @@ struct LibraryFilterView: View {
|
|||
}
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
self.filters = viewModel.modifyedFilters
|
||||
self.filters = viewModel.modifiedFilters
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
Text("Apply")
|
||||
|
|
|
@ -558,6 +558,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
print("Local playback engine starting.")
|
||||
mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl)
|
||||
mediaPlayer.play()
|
||||
sendPlayReport()
|
||||
|
||||
// 1 second = 10,000,000 ticks
|
||||
var startTicks: Int64 = 0;
|
||||
|
@ -595,9 +596,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
// Wait for captions to load
|
||||
delegate?.showLoadingView(self)
|
||||
|
||||
while mediaPlayer.numberOfSubtitlesTracks != shouldHaveSubtitleTracks {
|
||||
print("waiting \(String(mediaPlayer.numberOfSubtitlesTracks)) != \(String(shouldHaveSubtitleTracks))")
|
||||
}
|
||||
while mediaPlayer.numberOfSubtitlesTracks != shouldHaveSubtitleTracks {}
|
||||
|
||||
// Select default track & resume playback
|
||||
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
|
||||
|
@ -643,9 +642,8 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
}
|
||||
timeText.text = timeTextStr
|
||||
|
||||
let playbackProgress = Int64(remotePositionTicks) / manifest.runTimeTicks!
|
||||
print(playbackProgress)
|
||||
seekSlider.setValue(Float(playbackProgress), animated: true)
|
||||
let playbackProgress = Float(remotePositionTicks) / Float(manifest.runTimeTicks!)
|
||||
seekSlider.setValue(playbackProgress, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,11 +652,14 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
if let json = try? JSON(data: data) {
|
||||
let messageType = json["type"].string ?? ""
|
||||
if(messageType == "playbackprogress") {
|
||||
if(hasSentRemoteSeek == false) {
|
||||
hasSentRemoteSeek = true;
|
||||
sendJellyfinCommand(command: "Seek", options: [
|
||||
"position": Int(Float(manifest.runTimeTicks! / 10_000_000) * mediaPlayer.position)
|
||||
])
|
||||
dump(json)
|
||||
if(remotePositionTicks > 100) {
|
||||
if(hasSentRemoteSeek == false) {
|
||||
hasSentRemoteSeek = true;
|
||||
sendJellyfinCommand(command: "Seek", options: [
|
||||
"position": Int(Float(manifest.runTimeTicks! / 10_000_000) * mediaPlayer.position)
|
||||
])
|
||||
}
|
||||
}
|
||||
paused = json["data"]["PlayState"]["IsPaused"].boolValue
|
||||
self.remotePositionTicks = json["data"]["PlayState"]["PositionTicks"].int ?? 0;
|
||||
|
@ -687,6 +688,20 @@ extension PlayerViewController: GCKGenericChannelDelegate {
|
|||
let jsonData = JSON(payload)
|
||||
|
||||
jellyfinCastChannel?.sendTextMessage(jsonData.rawString()!, error: nil)
|
||||
|
||||
if(command == "Seek") {
|
||||
remotePositionTicks = remotePositionTicks + ((options["position"] as! Int) * 10_000_000)
|
||||
//Send playback report as Jellyfin Chromecast isn't smarter than a rock.
|
||||
let progressInfo = PlaybackProgressInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: paused, isMuted: false, positionTicks: Int64(remotePositionTicks), playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0")
|
||||
|
||||
PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: progressInfo)
|
||||
.sink(receiveCompletion: { result in
|
||||
print(result)
|
||||
}, receiveValue: { _ in
|
||||
print("Playback progress report sent!")
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,14 +34,14 @@ extension SortBy {
|
|||
case .name:
|
||||
return "Title"
|
||||
case .dateAdded:
|
||||
return "Date added"
|
||||
return "Date Added"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ItemFilter {
|
||||
static var supportedTypes: [ItemFilter] {
|
||||
[.isUnplayed, isPlayed, .isFavorite, .likes, .isFavoriteOrLikes]
|
||||
[.isUnplayed, isPlayed, .isFavorite, .likes]
|
||||
}
|
||||
|
||||
var localized: String {
|
||||
|
@ -53,9 +53,7 @@ extension ItemFilter {
|
|||
case .isFavorite:
|
||||
return "Favorites"
|
||||
case .likes:
|
||||
return "Liked"
|
||||
case .isFavoriteOrLikes:
|
||||
return "Favorites or Liked"
|
||||
return "Liked Items"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ enum FilterType {
|
|||
|
||||
final class LibraryFilterViewModel: ViewModel {
|
||||
@Published
|
||||
var modifyedFilters = LibraryFilters()
|
||||
var modifiedFilters = LibraryFilters()
|
||||
|
||||
@Published
|
||||
var possibleGenres = [NameGuidPair]()
|
||||
|
@ -41,7 +41,7 @@ final class LibraryFilterViewModel: ViewModel {
|
|||
self.enabledFilterType = enabledFilterType
|
||||
super.init()
|
||||
if let filters = filters {
|
||||
self.modifyedFilters = filters
|
||||
self.modifiedFilters = filters
|
||||
}
|
||||
requestQueryFilters()
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>43</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
Loading…
Reference in New Issue