Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 .m3u8 adaptive 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 .vtt text track, with language and label.
  • Unlisted videos work. Private share links like vimeo.com/123456/abc123 resolve 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

Why isn't my video playing?

Almost every failure is a Vimeo privacy setting, not a bug. Vimeo has two settings that work independently, and this catches nearly everyone out:

  • "Who can watch?" controls vimeo.com.
  • "Where can this be embedded?" controls everywhere else — including your app.

A video can be perfectly public on vimeo.com and still refuse to play in your app, because the second setting says so. That is why "but it works in my browser" is not evidence that the link is fine.

Symptom Cause Fix
Plays in a browser, not in your app Embed privacy On Vimeo: Share → "Where can this be embedded?" → Anywhere
"Because of its privacy settings, this video cannot be played here" Domain-level embed privacy Same as above. "Specific domains" can never work in an app — an app has no domain to whitelist
Worked earlier, fails now The stream URL expired Don't store stream URLs. Store the Vimeo link and resolve again
Some videos work, others don't Privacy is set per video Check the failing video's own settings, then set an account-wide default
Unlisted video won't play The link lost its privacy hash Use the whole link — vimeo.com/123456789/abc123def, not just the number
Password-protected video Not supported Cannot be played. Remove the password

The package tells you which of these it is. Each maps to an error you can catch:

Error Meaning
VimeoEmbedNotAllowedException Embed privacy blocks apps. Only the owner can fix it
VimeoPrivateLinkRequiredException Unlisted video, and the link had no hash
VimeoNotFoundException The video really is gone
VimeoRestrictedException Withheld for some other reason (password, fully private)

Every one of these carries Vimeo's own explanation in .reason when Vimeo gave one, so your logs show exactly what Vimeo said.

This package cannot and will not bypass Vimeo's privacy settings. If a video is restricted, the owner has decided that deliberately, and the only fix is on Vimeo. Any package claiming otherwise is either breaking that choice or about to stop working.


Install it

Step 1. Open your pubspec.yaml file and add this under dependencies:

dependencies:
  vimeo_native_player: ^1.1.0

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
}

Playing your own private videos (optional)

Everything above works with no Vimeo account. But if you own the videos and keep them private, no public route can reach them — that is what private means. For that case only, you can supply a Vimeo API access token:

final resolver = VimeoResolver(accessToken: 'your-token');
final video = await resolver.resolve('https://vimeo.com/76979871');

The token is used only after the normal routes have failed, so ordinary public videos cost no extra request. It must belong to the account that owns the video, and needs the video_files scope to return playable files (a paid Vimeo plan). Get one from the Vimeo developer console.

⚠️ Never ship a token inside your app

Anything inside a released app can be pulled back out of it — an API token is not a secret once it is on someone else's phone. A leaked token gives whoever finds it control of your whole Vimeo account.

Do this instead: keep the token on a server you control. Have your app ask your server for the video, let the server resolve it, and return just the stream URL. The URL expires within the hour, so it is safe to hand out; the token is not.

Subtitles are not returned on this route — Vimeo publishes those on a separate endpoint, and fetching it would cost a second request on every resolve. Use the ordinary tokenless route if you need captions.


⚠️ 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 VimeoEmbedNotAllowedException {
  print('The owner must allow this video to be embedded anywhere.');
} on VimeoPrivateLinkRequiredException {
  print('Use the full private link, including the hash.');
} 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
}

Order matters: put the specific errors before the general ones, or the general one catches everything first.

Error What it means What to do
VimeoInvalidUrlException Not a Vimeo video link Check the link
VimeoNotFoundException Video was deleted Nothing you can do
VimeoEmbedNotAllowedException Embed privacy blocks apps Owner sets embedding to "Anywhere"
VimeoPrivateLinkRequiredException Unlisted link lost its hash Use the full link with the hash
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

The last two are subtypes of VimeoRestrictedException, so code that already catches that keeps working unchanged.

Every error carries a message safe to show a user. Refusals also carry .reason — Vimeo's own sentence, such as "Because of its privacy settings, this video cannot be played here." — which is what you want in your logs.


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? Unlisted videos work with no setup — use the whole link, including the hash (vimeo.com/123456/abc123). Videos you own that are fully private need an access token. Other people's private and password-protected videos will not play: that is the owner's choice, and we respect it.

It plays on vimeo.com but not in my app. Why? Vimeo's embed privacy is separate from its viewing privacy. See Why isn't my video playing? — this is the single most common problem, and the fix is on Vimeo, not in your code.

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 bypassing privacy settings. If a video's embed privacy excludes apps, only its owner can change that, on Vimeo. The package will tell you that is what happened — it will not work around it.
  • 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.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages