diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj index 4ca96fbb..2bed2b9f 100644 --- a/Aerial.xcodeproj/project.pbxproj +++ b/Aerial.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + AA7E2E5E1FC62E8B00E5F320 /* AerialPlayerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E2E5D1FC62E8B00E5F320 /* AerialPlayerItem.swift */; }; FA143CE61BDA3EEF0041A82B /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA143CE51BDA3EEF0041A82B /* AVKit.framework */; }; FA36BD3F1BE57F8E00D5E03B /* VideoDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA36BD3E1BE57F8E00D5E03B /* VideoDownload.swift */; }; FA36BD401BE57F8E00D5E03B /* VideoDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA36BD3E1BE57F8E00D5E03B /* VideoDownload.swift */; }; @@ -57,6 +58,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + AA7E2E5D1FC62E8B00E5F320 /* AerialPlayerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AerialPlayerItem.swift; sourceTree = ""; }; FA143CD61BDA3E880041A82B /* AerialApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AerialApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; FA143CE51BDA3EEF0041A82B /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; FA36BD3E1BE57F8E00D5E03B /* VideoDownload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = VideoDownload.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -218,6 +220,7 @@ children = ( FAC36F431BE1756D007F2A20 /* AerialView.swift */, FAC36F441BE1756D007F2A20 /* CheckCellView.swift */, + AA7E2E5D1FC62E8B00E5F320 /* AerialPlayerItem.swift */, ); path = Views; sourceTree = ""; @@ -454,6 +457,7 @@ FAC36F571BE1756D007F2A20 /* PreferencesWindowController.swift in Sources */, FAC36F671BE1778C007F2A20 /* ManifestLoader.swift in Sources */, FAC36F591BE1756D007F2A20 /* AerialVideo.swift in Sources */, + AA7E2E5E1FC62E8B00E5F320 /* AerialPlayerItem.swift in Sources */, FAF450211BE2B45D00C1F98A /* VideoLoader.swift in Sources */, FAC36F6A1BE1780B007F2A20 /* Debug.swift in Sources */, FAB22A7E1BE17D7D0065C0F5 /* AssetLoaderDelegate.swift in Sources */, diff --git a/Aerial/Resources/PreferencesWindow.xib b/Aerial/Resources/PreferencesWindow.xib index fb8cd815..b5943e39 100644 --- a/Aerial/Resources/PreferencesWindow.xib +++ b/Aerial/Resources/PreferencesWindow.xib @@ -26,7 +26,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -202,7 +202,7 @@ - + diff --git a/Aerial/Source/Models/AerialVideo.swift b/Aerial/Source/Models/AerialVideo.swift index 8b74b8ef..4c62d071 100644 --- a/Aerial/Source/Models/AerialVideo.swift +++ b/Aerial/Source/Models/AerialVideo.swift @@ -8,7 +8,11 @@ import Foundation -class AerialVideo: CustomStringConvertible { +class AerialVideo: CustomStringConvertible, Equatable { + static func ==(lhs: AerialVideo, rhs: AerialVideo) -> Bool { + return lhs.id == rhs.id && lhs.url == rhs.url + } + let id: String let name: String let type: String diff --git a/Aerial/Source/Models/ManifestLoader.swift b/Aerial/Source/Models/ManifestLoader.swift index 609962c2..3796eb43 100644 --- a/Aerial/Source/Models/ManifestLoader.swift +++ b/Aerial/Source/Models/ManifestLoader.swift @@ -28,7 +28,7 @@ class ManifestLoader { } } - func randomVideo() -> AerialVideo? { + func randomVideo(excluding: [AerialVideo]) -> AerialVideo? { let shuffled = loadedManifest.shuffled() for video in shuffled { let inRotation = preferences.videoIsInRotation(videoID: video.id) @@ -38,6 +38,11 @@ class ManifestLoader { continue } + if excluding.contains(video) { + debugLog("video is excluded because it's already in use: \(video)") + continue + } + // check if we're in offline mode if offlineMode == true { if video.isAvailableOffline == false { diff --git a/Aerial/Source/Views/AerialPlayerItem.swift b/Aerial/Source/Views/AerialPlayerItem.swift new file mode 100644 index 00000000..9e52780e --- /dev/null +++ b/Aerial/Source/Views/AerialPlayerItem.swift @@ -0,0 +1,20 @@ +// +// AerialPlayerItem.swift +// Aerial +// +// Created by Ethan Setnik on 11/22/17. +// Copyright © 2017 John Coates. All rights reserved. +// +import AVFoundation +import AVKit + +class AerialPlayerItem: AVPlayerItem { + var video: AerialVideo? + + init(video: AerialVideo) { + let videoURL = video.url + let asset = CachedOrCachingAsset(videoURL) + super.init(asset: asset, automaticallyLoadedAssetKeys: nil) + self.video = video + } +} diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift index cfb537fa..958f1d78 100644 --- a/Aerial/Source/Views/AerialView.swift +++ b/Aerial/Source/Views/AerialView.swift @@ -194,8 +194,6 @@ class AerialView: ScreenSaverView { debugLog("playing next video for player \(String(describing: player))") } - // MARK: - Playing Videos - func playNextVideo() { let notificationCenter = NotificationCenter.default @@ -221,18 +219,22 @@ class AerialView: ScreenSaverView { AerialView.previewView?.playerLayer.player = self.player } - let randomVideo = ManifestLoader.instance.randomVideo() + // get a list of current videos that should be excluded from the candidate selection + // for the next video. This prevents the same video from being shown twice in a row + // as well as the same video being shown on two different monitors even when sharingPlayers + // is false + let currentVideos: [AerialVideo] = AerialView.players.flatMap { (player) -> AerialVideo? in + (player.currentItem as? AerialPlayerItem)?.video + } + + let randomVideo = ManifestLoader.instance.randomVideo(excluding: currentVideos) guard let video = randomVideo else { NSLog("Aerial: Error grabbing random video!") return } - let videoURL = video.url - - let asset = CachedOrCachingAsset(videoURL) -// let asset = AVAsset(URL: videoURL) - let item = AVPlayerItem(asset: asset) + let item = AerialPlayerItem(video: video) player.replaceCurrentItem(with: item)