Update ContentView.swift

This commit is contained in:
Anirudh Sevugan 2025-04-17 15:58:22 -05:00 committed by GitHub
parent 4ef9050134
commit b6fe4064db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,395 +1,215 @@
import SwiftUI import SwiftUI
import AVKit import AVKit
import SafariServices import SafariServices
import UIKit import UniformTypeIdentifiers
struct ContentView: View { struct ContentView: View {
@State var videoURL: String = "" @State private var videoURL: String = ""
@State var videoFileURL: URL? @State private var videoFileURL: URL?
@State var audioFileURL: URL? @State private var audioFileURL: URL?
@State private var isMenuOpen = false @State private var isMenuOpen = false
@State private var showWebView = false @State private var showWebView = false
@State private var urlToOpen: String? @State private var urlToOpen: String?
@State private var player: AVPlayer? @State private var player: AVPlayer?
@State private var playerObserver: Any?
@State private var playlist: [String] = [] // Playlist array @State private var isPickingVideo = false
@State private var currentVideoIndex: Int = 0 // Track the current video in the playlist @State private var isPickingAudio = false
@State private var customURL: String = "" // For custom URL input
let playlistKey = "playlistKey" // UserDefaults key
// Load playlist from UserDefaults
init() {
if let savedPlaylist = UserDefaults.standard.array(forKey: playlistKey) as? [String] {
self._playlist = State(initialValue: savedPlaylist)
}
}
var body: some View { var body: some View {
NavigationView { NavigationView {
ZStack { ZStack {
VStack { ScrollView {
// Video URL input VStack(spacing: 20) {
TextField("Enter Video URL", text: $videoURL) Text("🎬 SimpliPlay")
.padding() .font(.largeTitle)
.textFieldStyle(RoundedBorderTextFieldStyle()) .fontWeight(.bold)
.frame(width: 300) .padding(.top)
Button("Play Video") { // Video URL Control
playVideo(urlString: videoURL) VStack(spacing: 10) {
} TextField("Enter Media URL", text: $videoURL)
.padding() .textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 300) .padding(.horizontal)
// Clear Video URL button HStack {
Button("Clear Video URL") { Button("Play Media") {
videoURL = "" // Clear the video URL playVideo(urlString: videoURL)
} }
.padding() .buttonStyle(PrimaryButtonStyle())
.frame(width: 300)
.foregroundColor(.red)
// File Picker Buttons Button("Clear") {
HStack { videoURL = ""
Button("Choose Video File") { }
pickFile(type: .movie) .foregroundColor(.red)
}
} }
.padding()
// File Picker Section
Button("Choose Audio File") { VStack(spacing: 12) {
pickFile(type: .audio) HStack {
Button("Choose Video File", action: { isPickingVideo = true })
Button("Choose Audio File", action: { isPickingAudio = true })
}
if let video = videoFileURL {
Text("🎞️ \(video.lastPathComponent)")
Button("Play Selected Video") {
playLocalVideo(fileURL: video)
}
.buttonStyle(PrimaryButtonStyle())
}
if let audio = audioFileURL {
Text("🎵 \(audio.lastPathComponent)")
}
} }
.padding() .padding(.horizontal)
}
// Display selected files Button("Open Menu") {
if let videoURL = videoFileURL {
Text("Video Selected: \(videoURL.lastPathComponent)")
.padding()
}
if let audioURL = audioFileURL {
Text("Audio Selected: \(audioURL.lastPathComponent)")
.padding()
}
// Add custom URL to playlist
VStack {
TextField("Enter Custom URL", text: $customURL)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 300)
Button("Add to Playlist") {
addCustomURLToPlaylist(url: customURL)
}
.padding()
.frame(width: 300)
}
// Side menu button
Button("Open Menu") {
isMenuOpen.toggle()
}
.padding()
Spacer()
// Playlist Button
NavigationLink(destination: PlaylistView(playlist: $playlist, onLoad: loadPlaylist, onAdd: addToPlaylist, onDelete: deleteFromPlaylist)) {
Text("Manage Playlist")
.padding()
.frame(width: 300)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
Button("Play Playlist") {
playPlaylist()
}
.padding()
.frame(width: 300)
}
// Side menu overlay
if isMenuOpen {
VStack {
Button("About the Creator") {
urlToOpen = "https://a-star100.github.io"
showWebView.toggle()
}
.padding()
Button("Close Menu") {
isMenuOpen.toggle() isMenuOpen.toggle()
} }
.padding() .buttonStyle(PrimaryButtonStyle())
} }
.frame(width: 250) .padding()
.background(Color.gray.opacity(0.7))
.cornerRadius(10)
.padding(.top, 50)
.transition(.move(edge: .leading))
} }
// Safari WebView for opening URL if isMenuOpen {
if showWebView, let urlString = urlToOpen, let url = URL(string: urlString) { VStack(alignment: .leading, spacing: 20) {
SafariView(url: url) Button("Official Website") {
.edgesIgnoringSafeArea(.all) urlToOpen = "https://simpliplay.netlify.app"
showWebView = true
isMenuOpen = false
}
Button("About the Creator") {
urlToOpen = "https://a-star100.github.io"
showWebView = true
isMenuOpen = false
}
Button("Close Menu") {
isMenuOpen = false
}
}
.padding()
.frame(maxWidth: 250, alignment: .leading)
.background(Color(.systemGray6))
.cornerRadius(10)
.shadow(radius: 5)
.padding()
.transition(.move(edge: .leading))
}
}
.navigationBarHidden(true)
.sheet(isPresented: $showWebView) {
if let urlString = urlToOpen, let url = URL(string: urlString) {
SafariView(url: url)
}
}
.sheet(isPresented: $isPickingVideo) {
DocumentPicker(fileType: .movie) { url in
videoFileURL = url
}
}
.sheet(isPresented: $isPickingAudio) {
DocumentPicker(fileType: .audio) { url in
audioFileURL = url
} }
} }
.navigationBarTitle("SimpliPlay", displayMode: .inline)
} }
} }
// Play video function // MARK: - Video Playback Helpers
func playVideo(urlString: String) { func playVideo(urlString: String) {
if let url = URL(string: urlString) { if let url = URL(string: urlString) {
player = AVPlayer(url: url) player = AVPlayer(url: url)
let playerViewController = AVPlayerViewController() let controller = AVPlayerViewController()
playerViewController.player = player controller.player = player
playerViewController.modalPresentationStyle = .fullScreen controller.modalPresentationStyle = .fullScreen
present(playerViewController) present(controller)
} }
} }
// Play playlist function func playLocalVideo(fileURL: URL) {
func playPlaylist() { player = AVPlayer(url: fileURL)
guard !playlist.isEmpty else { return } let controller = AVPlayerViewController()
controller.player = player
// Start playing the first video in the playlist controller.modalPresentationStyle = .fullScreen
playVideoFromPlaylist(index: currentVideoIndex) present(controller)
} player?.play()
// Play video from playlist
func playVideoFromPlaylist(index: Int) {
let urlString = playlist[index]
if let url = URL(string: urlString) {
player = AVPlayer(url: url)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
playerViewController.modalPresentationStyle = .fullScreen
// Add observer to detect when video finishes playing
playerObserver = player?.addBoundaryTimeObserver(forTimes: [NSValue(time: CMTimeMakeWithSeconds(1.0, preferredTimescale: 600))], queue: .main) {
self.videoDidFinishPlaying()
}
present(playerViewController)
player?.play()
}
}
// When the video finishes playing
func videoDidFinishPlaying() {
// Move to the next video in the playlist
if currentVideoIndex < playlist.count - 1 {
currentVideoIndex += 1
playVideoFromPlaylist(index: currentVideoIndex)
} else {
currentVideoIndex = 0 // Loop back to the start
playVideoFromPlaylist(index: currentVideoIndex)
}
}
// File picker function
func pickFile(type: FileType) {
let documentPicker: UIDocumentPickerViewController
if type == .movie {
documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.movie], asCopy: true)
} else {
documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.audio], asCopy: true)
}
let coordinator = DocumentPickerCoordinator(parent: self)
documentPicker.delegate = coordinator
documentPicker.modalPresentationStyle = .formSheet
present(documentPicker)
} }
func present(_ viewController: UIViewController) { func present(_ viewController: UIViewController) {
if let rootVC = UIApplication.shared.connectedScenes if let rootVC = UIApplication.shared.connectedScenes
.compactMap({ ($0 as? UIWindowScene)?.windows.first }) .compactMap({ ($0 as? UIWindowScene)?.windows.first?.rootViewController }).first {
.first?.rootViewController {
rootVC.present(viewController, animated: true) rootVC.present(viewController, animated: true)
} }
} }
// Save playlist to UserDefaults
func savePlaylist() {
UserDefaults.standard.set(playlist, forKey: playlistKey)
}
// Add item to playlist
func addToPlaylist(url: String) {
playlist.append(url)
savePlaylist() // Save after adding
}
// Add custom URL to the playlist
func addCustomURLToPlaylist(url: String) {
// Make sure URL is valid
if let validURL = URL(string: url), UIApplication.shared.canOpenURL(validURL) {
playlist.append(url)
savePlaylist() // Save after adding
}
}
// Load playlist from URL
func loadPlaylist(url: String) {
if let playlistURL = URL(string: url) {
// Mock loading playlist
downloadAndParseM3U(from: playlistURL)
}
}
// Parse M3U playlist and add items to the playlist
func downloadAndParseM3U(from url: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print("Error downloading playlist")
return
}
if let playlistText = String(data: data, encoding: .utf8) {
let urls = self.parseM3U(playlistText)
DispatchQueue.main.async {
self.playlist = urls
self.savePlaylist()
}
}
}
task.resume()
}
// Simple M3U Parser
func parseM3U(_ text: String) -> [String] {
let lines = text.split(separator: "\n")
var urls: [String] = []
for line in lines {
let trimmedLine = line.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedLine.isEmpty && !trimmedLine.hasPrefix("#") {
urls.append(String(trimmedLine))
}
}
return urls
}
// Delete item from playlist
func deleteFromPlaylist(at index: Int) {
playlist.remove(at: index)
savePlaylist() // Save after deleting
}
} }
// Enum for file types // MARK: - Primary Button Style
enum FileType {
case movie, audio
}
// Playlist management view struct PrimaryButtonStyle: ButtonStyle {
struct PlaylistView: View { func makeBody(configuration: Configuration) -> some View {
@Binding var playlist: [String] // Bind to the playlist array configuration.label
var onLoad: (String) -> Void // Callback to load playlist from URL
var onAdd: (String) -> Void // Callback to add item to playlist
var onDelete: (Int) -> Void // Callback to delete item from playlist
@State private var newPlaylistURL: String = ""
var body: some View {
VStack {
// TextField to enter playlist URL
TextField("Enter Playlist URL", text: $newPlaylistURL)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 300)
// Button to load the playlist from the entered URL
Button("Load Playlist") {
onLoad(newPlaylistURL)
}
.padding() .padding()
.frame(width: 300) .frame(maxWidth: .infinity)
.background(Color.blue) .background(configuration.isPressed ? Color.blue.opacity(0.6) : Color.blue)
.foregroundColor(.white) .foregroundColor(.white)
.cornerRadius(8) .cornerRadius(12)
.disabled(newPlaylistURL.isEmpty) // Disable if the URL field is empty .padding(.horizontal)
// List to display current playlist
List {
ForEach(playlist.indices, id: \.self) { index in
HStack {
Text(playlist[index])
Spacer()
Button(action: {
onDelete(index)
}) {
Image(systemName: "trash.fill")
.foregroundColor(.red)
}
}
}
}
.navigationBarTitle("Playlist", displayMode: .inline)
}
.navigationBarItems(trailing: Button(action: {
// Replace this with adding custom URL to playlist
onAdd(newPlaylistURL)
}) {
Text("Add Item")
})
} }
} }
// Safari WebView for URL opening // MARK: - Safari View
struct SafariView: View { struct SafariView: View {
var url: URL var url: URL
var body: some View { var body: some View {
SafariViewController(url: url) SafariViewController(url: url)
.edgesIgnoringSafeArea(.all) // Make it full screen .edgesIgnoringSafeArea(.all)
} }
} }
// Safari WebView Controller (UIKit Wrapper)
struct SafariViewController: UIViewControllerRepresentable { struct SafariViewController: UIViewControllerRepresentable {
let url: URL let url: URL
func makeUIViewController(context: Context) -> SFSafariViewController { func makeUIViewController(context: Context) -> SFSafariViewController {
return SFSafariViewController(url: url) SFSafariViewController(url: url)
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
// No update needed for this view controller
} }
func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {}
} }
// Document Picker Coordinator (Delegate) // MARK: - Document Picker
class DocumentPickerCoordinator: NSObject, UIDocumentPickerDelegate {
var parent: ContentView struct DocumentPicker: UIViewControllerRepresentable {
var fileType: UTType
init(parent: ContentView) { var onPick: (URL) -> Void
self.parent = parent
func makeCoordinator() -> Coordinator {
Coordinator(onPick: onPick)
} }
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
// Handle file selection let picker = UIDocumentPickerViewController(forOpeningContentTypes: [fileType], asCopy: true)
if let url = urls.first { picker.delegate = context.coordinator
if url.pathExtension == "mp4" || url.pathExtension == "mov" { return picker
parent.videoFileURL = url }
} else if url.pathExtension == "mp3" || url.pathExtension == "m4a" {
parent.audioFileURL = url func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {}
class Coordinator: NSObject, UIDocumentPickerDelegate {
let onPick: (URL) -> Void
init(onPick: @escaping (URL) -> Void) {
self.onPick = onPick
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
if let url = urls.first {
onPick(url)
} }
} }
} }
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
// Handle cancellation
print("Document picker was cancelled")
}
} }