mirror of
https://github.com/A-Star100/simpliplay-ios.git
synced 2025-09-18 06:49:46 +00:00
Update ContentView.swift
This commit is contained in:
parent
4ef9050134
commit
b6fe4064db
@ -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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user