Since I build app similar to Apple' Mindfulness app I was curious how they do it. Then I noticed that there is something which looks like video being played. So I started experimenting with playing video and it worked - display didn't turn off.
Point is, you need to use native VideoPlayer
and then play video in loop. It is great that this view doesn't even have to be visible, so you can just put it in the background with nearly zero opacity.
let audioURL = Bundle.main.url(forResource: "video", withExtension: "mp4")
...
let player = AVPlayer(url: audioURL)
...
.background(
VideoPlayer(player: player)
.opacity(0)
)
Then you just need to make sure that you loop the video playing. You can achieve this by seeking player to the beginning after some time (e.g. 1 second). This also allows you to have just small video file which doesn't take too much space.
private var avPlayerPlayTask: Task<Void, Never>?
...
func startAVPlayerPlayTask() {
avPlayerPlayTask?.cancel()
avPlayerPlayTask = Task {
await player.seek(to: .zero)
player.play()
try? await Task.sleep(nanoseconds: UInt64(1 * 1_000_000_000))
guard !Task.isCancelled else { return }
startAVPlayerPlayTask()
}
One last thing. Once you leave the screen when you need this, don't forget to cancel the task and stop the player since having player playing all the time can be heavy in terms of battery effeciency.
Fully working code (don't forget to create some short video called video
and add it to bundle):
struct PlayerView: View {
@ObservedObject var viewModel: PlayerViewModel
var body: some View {
Text("Hello World!")
.background(
VideoPlayer(player: viewModel.player)
.opacity(0)
)
.onAppear { viewModel.handleAppear() }
.onDisappear { viewModel.handleDisappear() }
}
}
@MainActor final class PlayerViewModel: ObservableObject {
var player = AVPlayer()
func handleAppear() {
guard let url = Bundle.main.url(forResource: "video", withExtension: "mp4") else { return }
player.replaceCurrentItem(with: AVPlayerItem(url: url))
startAVPlayerPlayTask()
}
func handleDisappear() {
avPlayerPlayTask?.cancel()
player.replaceCurrentItem(with: nil)
}
private var avPlayerPlayTask: Task<Void, Never>?
private func startAVPlayerPlayTask() {
avPlayerPlayTask?.cancel()
avPlayerPlayTask = Task {
await player.seek(to: .zero)
player.play()
try? await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
guard !Task.isCancelled else { return }
startAVPlayerPlayTask()
}
}
}