Skip to content

Commit

Permalink
Merge pull request #24 from nhiroyasu/develop
Browse files Browse the repository at this point in the history
1.6.0
  • Loading branch information
nhiroyasu authored Jan 13, 2025
2 parents fadd1ec + cef7de8 commit 806d7a5
Show file tree
Hide file tree
Showing 31 changed files with 954 additions and 62 deletions.
74 changes: 65 additions & 9 deletions wallpaper-play.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion wallpaper-play/AppContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ class AppContainer {
container.register((any YouTubeContentsService).self) { _ in YouTubeContentsServiceImpl()}
container.register((any FileSelectionManager).self) { _ in FileSelectionManagerImpl()}
container.register((any AVPlayerManager).self) { _ in AVPlayerManagerImpl()}
container.register((any SettingWindowService).self) { injector in SettingWindowServiceImpl() }
container.register((any SettingWindowService).self) { _ in SettingWindowServiceImpl() }
container.register((any CameraDeviceService).self) { _ in CameraDeviceServiceImpl() }
container.register(AppState.self) { _ in AppState.shared }.inObjectScope(.container)

// MARK: - DockMenu

container.register((any DockMenuUseCase).self) { injector in DockMenuInteractor(injector: injector) }
container.register((any DockMenuAction).self) { injector in DockMenuActionImpl(injector: injector)}
container.register((any DockMenuItemBuilder).self) { injector in DockMenuItemBuilderImpl(injector: injector) }
Expand Down
8 changes: 8 additions & 0 deletions wallpaper-play/AppState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class AppState {
static let shared = AppState()

private init() {}

var wallpaperKind: WallpaperKind? = nil
}

20 changes: 20 additions & 0 deletions wallpaper-play/Entity/CameraWallpaper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation
import RealmSwift

// NOTE: Added at realm schema version 7.
class CameraWallpaper: Object, DateSortable {
@Persisted var date: Date
@Persisted var deviceId: String
@Persisted var size: Int

convenience init(
date: Date,
deviceId: String,
size: Int
) {
self.init()
self.date = date
self.deviceId = deviceId
self.size = size
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation
import AppKit
import Swinject
import Injectable

class CameraSelectionCoordinator: Coordinator {
private var viewController: CameraSelectionViewController!
private let injector: any Injectable

init(injector: any Injectable) {
self.injector = injector
}

func create() -> NSViewController {
let useCase = CameraSelectionInteractor(
wallpaperRequestService: injector.build()
)
let presenter = CameraSelectionPresenterImpl(
useCase: useCase,
alertManager: injector.build(),
fileSelectionService: injector.build()
)
viewController = CameraSelectionViewController(
presenter: presenter,
notificationManager: injector.build(),
cameraDeviceService: injector.build(),
appState: injector.build()
)
presenter.output = viewController
return viewController
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation
import AppKit
import AVFoundation
import Injectable

protocol CameraSelectionPresenter {
func didTapSetWallpaperButton(selectedCamera: AVCaptureDevice, videoSize: VideoSize)
}

class CameraSelectionPresenterImpl: CameraSelectionPresenter {
private let useCase: any CameraSelectionUseCase
private let fileSelectionService: any FileSelectionManager
private let alertManager: any AlertManager
weak var output: (any CameraSelectionViewOutput)!

init(
useCase: any CameraSelectionUseCase,
alertManager: any AlertManager,
fileSelectionService: any FileSelectionManager
) {
self.useCase = useCase
self.alertManager = alertManager
self.fileSelectionService = fileSelectionService
}

func didTapSetWallpaperButton(selectedCamera: AVCaptureDevice, videoSize: VideoSize) {
useCase.requestSettingWallpaper(selectedCamera.uniqueID, videoSize: videoSize)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Injectable
import AVFoundation

protocol CameraSelectionUseCase {
func requestSettingWallpaper(_ deviceId: String, videoSize: VideoSize)
}

class CameraSelectionInteractor: CameraSelectionUseCase {

private let wallpaperRequestService: any WallpaperRequestService

init(wallpaperRequestService: any WallpaperRequestService) {
self.wallpaperRequestService = wallpaperRequestService
}

func requestSettingWallpaper(_ deviceId: String, videoSize: VideoSize) {
wallpaperRequestService.requestCameraWallpaper(
camera: CameraPlayValue(deviceId: deviceId, videoSize: videoSize)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import Cocoa
import AVFoundation
import Combine

protocol CameraSelectionViewOutput: AnyObject {
func setCamerasPopUpButton(titles: [String])
func selectCamera(selectedCamera: String)
}


class CameraSelectionViewController: NSViewController {
@IBOutlet weak var camerasPopUpButton: NSPopUpButton!
@IBOutlet weak var cameraDisplayView: NonInteractionView!
@IBOutlet weak var previewAnnotationLabel: NSTextField!
@IBOutlet weak var videoSizePopUpButton: NSPopUpButton! {
didSet {
videoSizePopUpButton.menu?.items = VideoSize.allCases.map {
.init(title: $0.text, action: nil, keyEquivalent: "")
}
videoSizePopUpButton.selectItem(at: 0)
}
}
private var previewLayer: AVCaptureVideoPreviewLayer!

let presenter: any CameraSelectionPresenter
let notificationManager: any NotificationManager
let cameraDeviceService: any CameraDeviceService
let appState: AppState
private var captureSession: AVCaptureSession!
private var cameraDevices: [AVCaptureDevice] = []
private var cancellable: Set<AnyCancellable> = []

private let captureSessionQueue = DispatchQueue(label: "com.nhiro1109.wallpaper-play.captureSessionQueue")

init(
presenter: any CameraSelectionPresenter,
notificationManager: any NotificationManager,
cameraDeviceService: any CameraDeviceService,
appState: AppState
) {
self.presenter = presenter
self.notificationManager = notificationManager
self.cameraDeviceService = cameraDeviceService
self.appState = appState
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

deinit {
captureSession.stopRunning()
}

override func viewDidLoad() {
super.viewDidLoad()

cameraDevices = cameraDeviceService.fetchDevices()

setUpPopUpButton()
setUpCameraPreview()
setPreviewAnnotationLabel()
observeWallpaperRequest()
}

override func viewDidAppear() {
super.viewDidAppear()
if case .camera = appState.wallpaperKind {
captureSessionQueue.async { [weak self] in
self?.captureSession.stopRunning()
}
previewAnnotationLabel.isHidden = false
} else {
captureSessionQueue.async { [weak self] in
self?.captureSession.startRunning()
}
previewAnnotationLabel.isHidden = true
}
}

override func viewDidDisappear() {
super.viewDidDisappear()
captureSessionQueue.async { [weak self] in
self?.captureSession.stopRunning()
}
previewAnnotationLabel.isHidden = false
}

@IBAction func didSelectCamera(_ sender: Any) {
if let selectedCamera = cameraDevices.first(where: { $0.localizedName == camerasPopUpButton.titleOfSelectedItem }) {
changeCameraPreview(device: selectedCamera)
}
}

@IBAction func didTapSetWallpaperButton(_ sender: Any) {
if let selectedCamera = cameraDevices.first(where: { $0.localizedName == camerasPopUpButton.titleOfSelectedItem }),
let videoSize = VideoSize(rawValue: videoSizePopUpButton.indexOfSelectedItem) {
presenter.didTapSetWallpaperButton(selectedCamera: selectedCamera, videoSize: videoSize)
}
}

// MARK: - Internal

func setUpPopUpButton() {
let titles = cameraDevices.map { $0.localizedName }
camerasPopUpButton.removeAllItems()
camerasPopUpButton.addItems(withTitles: titles)
}

func setUpCameraPreview() {
guard let camera = cameraDevices.first else { return }
captureSession = AVCaptureSession()

do {
let input = try AVCaptureDeviceInput(device: camera)
if captureSession.canAddInput(input) {
captureSessionQueue.async { [weak self] in
self?.captureSession.addInput(input)
}
}

previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = cameraDisplayView.bounds
previewLayer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable]

cameraDisplayView.layer = CALayer()
cameraDisplayView.layer?.addSublayer(previewLayer)

if case .camera = appState.wallpaperKind {
// do nothing
} else {
captureSessionQueue.async { [weak self] in
self?.captureSession.startRunning()
}
}
} catch {
print(error)
}
}

func setPreviewAnnotationLabel() {
if case .camera = appState.wallpaperKind {
previewAnnotationLabel.isHidden = false
} else {
previewAnnotationLabel.isHidden = true
}
}

func changeCameraPreview(device: AVCaptureDevice) {
captureSession.inputs.forEach { captureSession.removeInput($0) }
do {
let input = try AVCaptureDeviceInput(device: device)
if captureSession.canAddInput(input) {
captureSession.addInput(input)
}
} catch {
print(error)
}
}

func observeWallpaperRequest() {
// NOTE: When setting the camera as the wallpaper, the video stops when displaying the preview at the same time, so stop the preview when setting the camera as the wallpaper

notificationManager
.publisher(for: .requestCamera)
.sink { [weak self] _ in
self?.captureSessionQueue.async { [weak self] in
self?.captureSession.stopRunning()
}
self?.previewAnnotationLabel.isHidden = false
}
.store(in: &cancellable)

notificationManager
.publisher(for: .requestVideo)
.sink { [weak self] _ in
self?.previewAnnotationLabel.isHidden = true
}
.store(in: &cancellable)

notificationManager
.publisher(for: .requestYouTube)
.sink { [weak self] _ in
self?.previewAnnotationLabel.isHidden = true
}
.store(in: &cancellable)

notificationManager
.publisher(for: .requestWebPage)
.sink { [weak self] _ in
self?.previewAnnotationLabel.isHidden = true
}
.store(in: &cancellable)
}
}

extension CameraSelectionViewController: CameraSelectionViewOutput {
func setCamerasPopUpButton(titles: [String]) {
camerasPopUpButton.removeAllItems()
camerasPopUpButton.addItems(withTitles: titles)
}

func selectCamera(selectedCamera: String) {
camerasPopUpButton.selectItem(withTitle: selectedCamera)
}
}
Loading

0 comments on commit 806d7a5

Please sign in to comment.