vimeo_native_player
Play Vimeo videos inside your Flutter app — like any normal video.
VimeoNativePlayer(url: 'https://vimeo.com/76979871')
That is the whole setup. No Vimeo account. No API key. No sign up.
What you get
- No WebView. Plays the real video file with the native player, not an embedded Vimeo web page in a hidden browser.
- Direct HLS and MP4 streams. Resolves any Vimeo link to its
.m3u8adaptive stream or progressive MP4 URL, with every quality and resolution listed for you. - Autoplay with sound, looping, muting, seeking, and fullscreen — all the things a WebView embed cannot do reliably on mobile.
- Subtitles and captions. Every
.vtttext track, with language and label. - Unlisted videos work. Private share links like
vimeo.com/123456/abc123resolve normally. - Video metadata: title, duration, thumbnail, width, height, aspect ratio, fps, owner, live status, 360°/spatial flag.
- Use any player you like. Take the resolved stream URL and hand it to
video_player,chewie,better_player,media_kit, or your own. - Android, iOS, macOS and Web. Pure Dart — no platform channels of its own.
What problem does this solve?
A Vimeo link like vimeo.com/76979871 is a web page, not a video file.
Video players cannot play a web page. They need the address of the real video.
Other Vimeo packages solve this by putting the Vimeo website inside your app, in a small hidden browser (a "WebView"). That causes two real problems:
1. The video will not start on its own with sound. Phones block web pages from auto-playing sound. So the user must tap play, or the video plays silent.
2. The user can accidentally leave your app. The Vimeo page has real links on it (logo, title, author). One wrong tap opens a browser, and your user is gone.
This package works differently. It finds the address of the real video file and plays it with the phone's own video player — the same way your app plays any other video.
So:
| WebView packages | This package | |
|---|---|---|
| Starts automatically with sound | ✗ | ✓ |
| User can tap out to a browser | ✗ Yes, they can | ✓ No, they cannot |
| Looks like your app | ✗ Looks like a website | ✓ Native |
| Works on new Vimeo videos | Varies | ✓ |
Install it
Step 1. Open your pubspec.yaml file and add this under dependencies:
dependencies:
vimeo_native_player: ^1.0.1
Step 2. In your terminal, run:
flutter pub get
Step 3 (Android only). Open
android/app/src/main/AndroidManifest.xml and make sure this line is inside
the <manifest> tag:
<uses-permission android:name="android.permission.INTERNET"/>
Most apps already have it.
iPhone needs nothing extra.
Your first video (copy and paste this)
This is a complete, working app. Copy it into lib/main.dart and run it.
import 'package:flutter/material.dart';
import 'package:vimeo_native_player/vimeo_native_player.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('My Video')),
body: Center(
child: AspectRatio(
aspectRatio: 16 / 9,
child: VimeoNativePlayer(
url: 'https://vimeo.com/76979871',
),
),
),
),
);
}
}
One important rule
Always put the player inside something that gives it a size, like
AspectRatio or SizedBox. If you do not, Flutter does not know how big to
make the video, and you may see an error or a blank screen.
// Good — it has a size
AspectRatio(aspectRatio: 16 / 9, child: VimeoNativePlayer(url: myUrl))
// Also good
SizedBox(height: 220, child: VimeoNativePlayer(url: myUrl))
// Bad — no size given
VimeoNativePlayer(url: myUrl)
Which Vimeo links work?
All of these work:
https://vimeo.com/76979871
https://vimeo.com/76979871/abc123def
https://player.vimeo.com/video/76979871?h=abc123def
https://vimeo.com/channels/staffpicks/76979871
https://vimeo.com/groups/motion/videos/76979871
https://vimeo.com/album/2222222/video/76979871
https://vimeo.com/manage/videos/76979871
https://vimeo.com/user12345678/videos/76979871
https://vimeo.com/76979871?share=copy
The extra letters after the number (like abc123def) are for unlisted
videos — private share links that Vimeo gives you. Those work too. Just paste
the whole link.
Checking a link before using it
If your app has both YouTube and Vimeo links, you can check which is which:
if (VimeoUrl.isVimeo(myLink)) {
// show the Vimeo player
} else {
// show your YouTube player
}
Settings you can change
All of these are optional. Use only the ones you need.
VimeoNativePlayer(
url: 'https://vimeo.com/76979871',
autoPlay: true, // start by itself (default: true)
looping: false, // repeat forever when it ends (default: false)
muted: false, // start with no sound (default: false)
showControls: true, // show play/pause bar (default: true)
allowFullScreen: true, // let user go fullscreen (default: true)
startAt: Duration(seconds: 30), // begin 30 seconds in
useVideoAspectRatio: true, // match the video's own shape
progressColor: Colors.teal, // colour of the progress bar
)
Do something when the video loads
VimeoNativePlayer(
url: 'https://vimeo.com/76979871',
onResolved: (video) {
print('Title: ${video.title}');
print('Length: ${video.duration}');
},
onError: (error) {
print('Something went wrong: ${error.message}');
},
)
Show your own loading and error screens
VimeoNativePlayer(
url: 'https://vimeo.com/76979871',
loadingBuilder: (context) => const Center(
child: Text('Loading your video...'),
),
errorBuilder: (context, error, retry) => Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(error.message),
ElevatedButton(onPressed: retry, child: const Text('Try again')),
],
),
),
)
retry is a ready-made function. Put it on a button and it loads the video
again.
Using your own video player instead
You do not have to use our player. If you already use another player
(better_player, media_kit, or your own), just ask this package for the
video address and use it however you like.
final video = await VimeoResolver().resolve('https://vimeo.com/76979871');
print(video.title); // The New Vimeo Player
print(video.duration); // 0:01:02
print(video.thumbnailUrl); // the cover picture
print(video.bestStream!.url); // <-- the real video address
// Now give that address to any player you want
myOwnPlayer.open(video.bestStream!.url);
Everything you get back
| What you write | What you get |
|---|---|
video.title |
The video's name |
video.duration |
How long it is |
video.thumbnailUrl |
Cover picture address |
video.bestStream!.url |
The video address to play |
video.width, video.height |
Video size in pixels |
video.aspectRatio |
Its shape, e.g. 1.77 for widescreen |
video.fps |
Frames per second |
video.owner?.name |
Who uploaded it |
video.textTracks |
Subtitles (see below) |
video.isLive |
Is it a live broadcast? |
video.isSpatial |
Is it a 360° video? |
video.expiresAt |
When the video address stops working |
video.streams |
Every available address, best one first |
Subtitles
If the video has subtitles, you get them as a list:
for (final track in video.textTracks) {
print(track.label); // "English"
print(track.language); // "en"
print(track.url); // a .vtt subtitle file
print(track.isAutoGenerated); // true = made by computer, less accurate
}
⚠️ Very important: do not save the video address
The video address (bestStream!.url) stops working after about one hour.
Vimeo does this on purpose.
Wrong — saving the address in your database:
final video = await VimeoResolver().resolve(url);
database.save(video.bestStream!.url); // ✗ will break in an hour
Right — save the normal Vimeo link, and ask again each time:
database.save('https://vimeo.com/76979871'); // ✓ never expires
// later, when you want to play it:
final video = await VimeoResolver().resolve(savedLink);
The cover picture (thumbnailUrl) is different — that one does not
expire, so you can save it safely.
When things go wrong
Every error has a message that is safe to show your user.
try {
final video = await VimeoResolver().resolve(url);
} on VimeoNotFoundException {
print('This video was deleted.');
} on VimeoRestrictedException {
print('This video is private.');
} on VimeoNetworkException {
print('No internet. Please try again.');
} on VimeoException catch (e) {
print(e.message); // catches everything else
}
| Error | What it means | What to do |
|---|---|---|
VimeoInvalidUrlException |
Not a Vimeo video link | Check the link |
VimeoNotFoundException |
Video was deleted | Nothing you can do |
VimeoRestrictedException |
Private or password protected | Ask the owner |
VimeoNetworkException |
No internet | Offer a "Try again" button |
VimeoNoStreamException |
Vimeo has no playable file | Rare; check on Vimeo |
VimeoParseException |
Vimeo changed their website | Update this package |
Common questions
Do I need a Vimeo account or API key? No. Nothing at all.
Does it cost money? No. The package is free and Vimeo does not charge for this.
Does it work on iPhone and Android? Yes, both. Also macOS and web.
Can I play private videos?
Only unlisted videos (the ones with a secret link, like
vimeo.com/123456/abc123). Fully private or password-protected videos will
not play — that is the owner's choice and we respect it.
Why is the video slow to start? The first time, it asks Vimeo for the video address. After that it remembers for an hour, so it is instant.
My video is blank / I see an error about size.
You forgot to wrap it in AspectRatio or SizedBox. See
One important rule above.
Can I download the video? This package does not download videos. Please respect video owners' rights.
Things it cannot do
Being honest about the limits:
- No password-protected videos. You will get a
VimeoRestrictedException. - No DRM-protected videos. These will not play.
- 360° videos play flat. You can tell it is a 360 video with
video.isSpatial, but you cannot look around inside it. - No downloading. Streaming only.
- It depends on Vimeo. This package reads the same information Vimeo's own player reads. Vimeo has not promised to keep it the same forever. If they change it, videos stop playing and this package needs an update. If that happens, please open an issue.
For developers who want to help
flutter test # run all tests
flutter test --exclude-tags live # skip tests that need internet
Some tests talk to Vimeo for real. Those are the important ones — they are how we find out quickly if Vimeo changed something. Please keep them passing.
Bug reports and pull requests are welcome at the issue tracker.
License
MIT — free to use in any project, including commercial ones.
Libraries
- vimeo_native_player
- Play Vimeo videos natively in Flutter, with no WebView.