🎬 Playora
A production Flutter video player — HLS/MP4, a fully custom RTL/LTR gold-on-dark skin, quality / subtitle / audio menus, ads, gestures, playlists & seasons, and optional built-in analytics + resume.
#Live demo
The player itself, live — the same build that ships in the package: HLS with quality levels, playlist & seasons, subtitles and the full skin. Every feature toggle (ads, restriction, badge, …) lives in the full playground.
Gestures are mobile-first — open the demo full-page on a phone for the complete experience.
#Quick start
Add the git dependency, initialize media_kit once, then point the player at an HLS or MP4 source.
# pubspec.yaml
dependencies:
playora:
git:
url: https://github.com/Ali-Roodi/playora.git
void main() {
WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
runApp(const MyApp());
}
import 'package:playora/playora.dart';
PlayoraPlayer(
src: 'https://cdn.example.com/movie/master.m3u8',
poster: 'https://cdn.example.com/poster.jpg',
title: 'Sample Movie',
locale: PlayerLocale.fa, // fa (RTL) | en (LTR)
)
That's it — a self-contained player with zero network calls beyond the stream itself. Analytics, resume and providers are all opt-in.
#Features
#Episodes, seasons & up-next
Provide a list of episodes and the playlist panel with prev/next nav appears automatically. Episode.group renders sticky season headers, and near the end of an episode an up-next card (cover + filling progress) offers the next one — or auto-advances.
final episodes = [
Episode(id: 'e1', src: '.../e1.m3u8', title: 'سریال', subtitle: 'قسمت اول', group: 'فصل اول'),
Episode(id: 'e2', src: '.../e2.m3u8', title: 'سریال', subtitle: 'قسمت دوم', group: 'فصل اول'),
];
PlayoraPlayer(
episodes: episodes,
currentEpisodeId: current,
onEpisodeChange: (id) => setState(() => current = id),
)
#Ads
Pre-roll, mid-rolls (at content positions) and post-roll, with skip countdown and click-through. The player handles playback and resumes the content afterwards; ad playback is never counted in content analytics.
PlayoraPlayer(
src: src,
// Pre-roll shorthand:
ad: const AdConfig(src: 'https://ads.example.com/creative.m3u8', clickThrough: 'https://sponsor.example.com'),
// …or multiple breaks:
ads: [
const AdBreak(src: '...', offset: AdOffset.pre),
AdBreak(src: '...', offset: AdOffset.at(const Duration(minutes: 10))),
const AdBreak(src: '...', offset: AdOffset.post),
],
)
#Analytics & resume (optional)
Analytics is entirely optional — the player works fully without it. Pass a config and it emits canonical playback events to your ingest endpoint (batched, retried, heartbeats) and offers a “continue watching” banner from the saved position. No extra wiring.
PlayoraPlayer(
src: src,
analytics: const LogplexAnalyticsConfig(
baseUrl: 'https://ingest.example.com',
apiKey: '…', userId: 'viewer-1',
contentId: 'movie-42', contentType: 'movie',
),
resume: true, // continue-watching banner
)
#Your own back-end
No Logplex back-end needed: exchange an opaque play token via a VOD provider, report a periodic watch heartbeat to your own tracker, and feed the resume banner from your own API.
PlayoraPlayer(
src: playToken, // opaque token for the provider
vodType: VodProvider.abrHamrahi, // standard | abrHamrahi | poyan
qualityValidate: (height) => height > 400, // hide tiny renditions
onWatchInterval: (info) async {
final id = await reportWatch(info); // your tracker
return id; // chained into the next call
},
resolveResume: () async {
final w = await getWatch(contentId);
return w == null ? null : ResumePoint(position: Duration(seconds: w.seconds));
},
)
#Theming & localization
Theme via design tokens, switch between dark and light, and flip the language with locale. RTL only right-aligns text — controls, seek direction and gestures stay physical, like YouTube.
PlayoraPlayer(
appearance: PlayerAppearance.dark, // dark | light
theme: const PlayoraTheme(accent: Color(0xFFE8B84B)),
locale: PlayerLocale.fa, // RTL text; layout stays physical
strings: myCustomStrings, // override any label
)
#API overview
The most important PlayoraPlayer parameters. Everything is optional except a source (src / sources / episodes).
| Parameter | Type | Description |
|---|
#Platforms
Runs everywhere Flutter does — web uses hls.js under the hood, via media_kit.