Features: - VOD library with movie grouping and version detection - TV show library with season/episode organization - TMDB integration for trending shows and recently aired episodes - Recent releases section with TMDB release date sorting - Watch history tracking with continue watching - Playlist caching (12-hour TTL) for offline support - M3U playlist parsing with XStream API support - Authentication with credential storage Technical: - SwiftUI for tvOS - Actor-based services for thread safety - Persistent caching for playlists, TMDB data, and watch history - KSPlayer integration for video playback Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
418 lines
9.1 KiB
Markdown
418 lines
9.1 KiB
Markdown
# M3U Playlist Format Documentation
|
|
|
|
## File Location
|
|
`sample_playlist.m3u` - Example playlist showing XStream VOD format
|
|
|
|
---
|
|
|
|
## M3U Format Structure
|
|
|
|
### Header
|
|
```
|
|
#EXTM3U
|
|
```
|
|
- Required first line identifying the file as an M3U playlist
|
|
|
|
### Entry Format
|
|
Each VOD item consists of two lines:
|
|
|
|
**Line 1: Metadata (EXTINF)**
|
|
```
|
|
#EXTINF:-1 tvg-id="ID" tvg-name="NAME" tvg-logo="URL" group-title="CATEGORY",Display Title
|
|
```
|
|
|
|
**Line 2: Stream URL**
|
|
```
|
|
https://server.com/movie/username/password/12345.mp4
|
|
```
|
|
|
|
---
|
|
|
|
## Metadata Attributes
|
|
|
|
### tvg-id
|
|
- **Purpose**: Unique identifier for the video
|
|
- **Example**: `"movie001"`
|
|
- **Maps to**: `VODItem.id`
|
|
- **Used for**: Identifying specific content, tracking playback
|
|
|
|
### tvg-name
|
|
- **Purpose**: Clean video title (without year/metadata)
|
|
- **Example**: `"The Matrix"`
|
|
- **Maps to**: `VODItem.name` (if no comma-separated name exists)
|
|
- **Used for**: Display, search
|
|
|
|
### tvg-logo
|
|
- **Purpose**: Poster/thumbnail image URL
|
|
- **Example**: `"https://image.tmdb.org/t/p/w500/matrix.jpg"`
|
|
- **Maps to**: `VODItem.iconURL`
|
|
- **Used for**: Grid display, detail view
|
|
|
|
### group-title
|
|
- **Purpose**: Category/genre classification
|
|
- **Example**: `"Action Movies"`, `"Sci-Fi"`, `"Drama"`
|
|
- **Maps to**: `VODItem.categoryID`
|
|
- **Used for**: Category filtering, menu organization
|
|
|
|
### Display Title (after comma)
|
|
- **Purpose**: Full display name with year/additional info
|
|
- **Example**: `"The Matrix (1999)"`
|
|
- **Maps to**: `VODItem.name` (preferred over tvg-name)
|
|
- **Used for**: Primary display text
|
|
|
|
---
|
|
|
|
## Current Categories in Sample
|
|
|
|
Based on the sample playlist, we have these categories:
|
|
|
|
1. **Action Movies** (3 items)
|
|
- The Matrix, Inception, The Dark Knight
|
|
|
|
2. **Sci-Fi** (3 items)
|
|
- Interstellar, Blade Runner 2049, Arrival
|
|
|
|
3. **Drama** (3 items)
|
|
- The Shawshank Redemption, The Godfather, Forrest Gump
|
|
|
|
4. **Comedy** (3 items)
|
|
- Superbad, The Hangover, Anchorman
|
|
|
|
5. **Horror** (3 items)
|
|
- The Conjuring, Get Out, A Quiet Place
|
|
|
|
6. **Family & Kids** (3 items)
|
|
- Toy Story, Finding Nemo, The Lion King
|
|
|
|
7. **Thriller** (2 items)
|
|
- Parasite, Gone Girl
|
|
|
|
**Total: 7 categories, 20 movies**
|
|
|
|
---
|
|
|
|
## How Data is Parsed
|
|
|
|
### M3UParser.swift Logic
|
|
|
|
```swift
|
|
1. Read line: #EXTINF:-1 tvg-id="movie001" tvg-name="The Matrix" tvg-logo="..." group-title="Action Movies",The Matrix (1999)
|
|
|
|
2. Extract attributes:
|
|
- tvg-id → "movie001"
|
|
- tvg-name → "The Matrix"
|
|
- tvg-logo → "https://..."
|
|
- group-title → "Action Movies"
|
|
|
|
3. Extract display name (after comma):
|
|
- "The Matrix (1999)"
|
|
|
|
4. Read next line (stream URL):
|
|
- "https://widen.velvetfort.com/movie/..."
|
|
|
|
5. Create VODItem:
|
|
VODItem(
|
|
id: "movie001",
|
|
name: "The Matrix (1999)", // Preferred over tvg-name
|
|
streamURL: "https://...",
|
|
iconURL: "https://...",
|
|
categoryID: "Action Movies"
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## Menu Organization Options
|
|
|
|
### Option 1: Category-Based Navigation (Current)
|
|
|
|
**Main View**: Grid of all VOD items
|
|
**Filter Bar**: Horizontal scrolling category buttons
|
|
- All
|
|
- Action Movies
|
|
- Sci-Fi
|
|
- Drama
|
|
- Comedy
|
|
- Horror
|
|
- Family & Kids
|
|
- Thriller
|
|
|
|
**Pros**:
|
|
- Simple, flat navigation
|
|
- Easy to browse all content
|
|
- Quick category filtering
|
|
|
|
**Cons**:
|
|
- Large libraries can be overwhelming
|
|
- No hierarchy
|
|
|
|
---
|
|
|
|
### Option 2: Category Tabs
|
|
|
|
**TabView with categories as tabs**:
|
|
- Tab 1: All
|
|
- Tab 2: Action Movies
|
|
- Tab 3: Sci-Fi
|
|
- Tab 4: Drama
|
|
- etc.
|
|
|
|
**Pros**:
|
|
- Clear separation
|
|
- Native tvOS navigation
|
|
|
|
**Cons**:
|
|
- Limited to ~5-7 tabs before UI becomes crowded
|
|
- Not scalable for many categories
|
|
|
|
---
|
|
|
|
### Option 3: Hierarchical Menu
|
|
|
|
**Main Menu**:
|
|
- Browse All
|
|
- Categories ▸
|
|
- Action Movies ▸
|
|
- Sci-Fi ▸
|
|
- Drama ▸
|
|
- etc.
|
|
- Recently Added
|
|
- Continue Watching
|
|
|
|
**Pros**:
|
|
- Scalable to many categories
|
|
- Can add other browse methods
|
|
- Professional feel
|
|
|
|
**Cons**:
|
|
- More navigation depth
|
|
- Requires more views
|
|
|
|
---
|
|
|
|
### Option 4: Hybrid Approach (Recommended)
|
|
|
|
**Main View**: VODLibraryView (current implementation)
|
|
- Header with category filter (horizontal scroll)
|
|
- Grid of filtered content
|
|
- "All" shows everything
|
|
|
|
**Additional Features**:
|
|
- Search button
|
|
- Sort options (A-Z, Recently Added, etc.)
|
|
- Quick access to "Continue Watching" at top
|
|
|
|
**Implementation**:
|
|
```swift
|
|
VODLibraryView:
|
|
- All content by default
|
|
- Category pills at top (ScrollView horizontal)
|
|
- When category selected, filter grid
|
|
- Smooth animations between states
|
|
```
|
|
|
|
This is what is currently implemented.
|
|
|
|
---
|
|
|
|
## Data Structures
|
|
|
|
### Current Implementation
|
|
|
|
```swift
|
|
// Individual item
|
|
struct VODItem {
|
|
let id: String // tvg-id
|
|
let name: String // Display title (after comma)
|
|
let streamURL: String // Stream URL
|
|
let iconURL: String? // tvg-logo
|
|
let categoryID: String? // group-title
|
|
// Additional fields available for future use:
|
|
let description: String?
|
|
let duration: String?
|
|
let rating: String?
|
|
let added: String?
|
|
}
|
|
|
|
// Category
|
|
struct Category {
|
|
let id: String // Same as name for now
|
|
let name: String // group-title value
|
|
}
|
|
|
|
// Container
|
|
struct Playlist {
|
|
let vodItems: [VODItem]
|
|
let categories: [Category]
|
|
let lastUpdated: Date
|
|
}
|
|
```
|
|
|
|
### Accessing Data
|
|
|
|
```swift
|
|
// Get all items in a category
|
|
playlist.vodItems(forCategory: "Action Movies")
|
|
|
|
// Get categorized dictionary
|
|
let categorized = playlist.categorizedVODItems
|
|
// Returns: ["Action Movies": [VODItem], "Sci-Fi": [VODItem], ...]
|
|
|
|
// Current filtering (in VODLibraryViewModel)
|
|
func filteredVODItems(from playlist: Playlist?) -> [VODItem] {
|
|
var items = playlist.vodItems
|
|
|
|
if let selectedCategory = selectedCategory {
|
|
items = items.filter { $0.categoryID == selectedCategory }
|
|
}
|
|
|
|
if !searchText.isEmpty {
|
|
items = items.filter { $0.name.contains(searchText) }
|
|
}
|
|
|
|
return items
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Extending the Format
|
|
|
|
Your XStream provider may include additional attributes:
|
|
|
|
### Common Extended Attributes
|
|
|
|
```
|
|
#EXTINF:-1 tvg-id="ID"
|
|
tvg-name="NAME"
|
|
tvg-logo="URL"
|
|
group-title="CATEGORY"
|
|
tvg-duration="7200" // Duration in seconds
|
|
tvg-rating="8.7" // IMDB rating
|
|
tvg-year="1999" // Release year
|
|
tvg-country="USA" // Country
|
|
tvg-language="English" // Language
|
|
tvg-genre="Action|Sci-Fi" // Multiple genres
|
|
,Display Title
|
|
```
|
|
|
|
### To Support Additional Fields
|
|
|
|
Update `VODItem.swift`:
|
|
```swift
|
|
struct VODItem {
|
|
// ... existing fields ...
|
|
let duration: String? // Parse from tvg-duration
|
|
let rating: String? // Parse from tvg-rating
|
|
let year: String? // Parse from tvg-year
|
|
let country: String? // Parse from tvg-country
|
|
let language: String? // Parse from tvg-language
|
|
let genres: [String]? // Parse from tvg-genre (split by |)
|
|
}
|
|
```
|
|
|
|
Update `M3UParser.swift` to extract these attributes.
|
|
|
|
---
|
|
|
|
## Recommendations for Menu Organization
|
|
|
|
Based on typical VOD libraries:
|
|
|
|
### For Small Libraries (<100 items)
|
|
- Use current implementation (filter bar at top)
|
|
- Simple and effective
|
|
|
|
### For Medium Libraries (100-500 items)
|
|
- Add search functionality
|
|
- Add sort options (A-Z, Year, Rating)
|
|
- Keep category filter
|
|
|
|
### For Large Libraries (500+ items)
|
|
- Hierarchical navigation
|
|
- Featured/Recommended section
|
|
- Recently Added section
|
|
- Advanced filters (Year, Genre, Rating)
|
|
- Robust search with suggestions
|
|
|
|
---
|
|
|
|
## Current UI Implementation
|
|
|
|
Location: `VODLibraryView.swift`
|
|
|
|
### Header Section
|
|
```swift
|
|
- Title: "VOD Library"
|
|
- Refresh button
|
|
- Logout button
|
|
- Item count: "X videos"
|
|
- Category filter (horizontal scroll)
|
|
- All (default)
|
|
- Category buttons (generated from playlist.categories)
|
|
```
|
|
|
|
### Grid Section
|
|
```swift
|
|
- LazyVGrid (4 columns)
|
|
- VODCardView for each item
|
|
- Poster image
|
|
- Title
|
|
- Category label
|
|
- Focus effects
|
|
```
|
|
|
|
### State Management
|
|
```swift
|
|
VODLibraryViewModel:
|
|
- selectedCategory: String? // nil = "All"
|
|
- searchText: String // For future search
|
|
|
|
- filteredVODItems() returns filtered array
|
|
```
|
|
|
|
---
|
|
|
|
## Future Enhancements
|
|
|
|
1. **Search**: Add search bar, filter by name
|
|
2. **Sort**: Alphabetical, by year, by rating
|
|
3. **Favorites**: Star items, separate favorites view
|
|
4. **Continue Watching**: Track playback position
|
|
5. **Collections**: Featured, New Releases, Top Rated
|
|
6. **Multiple Genres**: Some items may fit multiple categories
|
|
7. **Metadata Display**: Show rating, year, duration on cards
|
|
|
|
---
|
|
|
|
## Testing Your Actual Playlist
|
|
|
|
Once you connect to your real XStream server:
|
|
|
|
1. **Authenticate** with the app
|
|
2. **Check Xcode console** for parsed playlist data
|
|
3. **Verify categories** extracted correctly
|
|
4. **Check metadata** (logos, titles, etc.)
|
|
5. **Test filtering** by selecting different categories
|
|
6. **Verify stream URLs** are correct format
|
|
|
|
If your actual playlist format differs from this sample, the M3UParser may need adjustments.
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Current State**:
|
|
- Functional category-based filtering
|
|
- Grid display with focus effects
|
|
- Horizontal category selection
|
|
|
|
**Format**:
|
|
- Standard M3U with XStream attributes
|
|
- Extracts: id, name, logo, category, stream URL
|
|
|
|
**Next Steps**:
|
|
- Test with your actual XStream playlist
|
|
- Adjust parser if format differs
|
|
- Consider additional features based on content volume
|