flutter_oembed 1.0.0-beta
flutter_oembed: ^1.0.0-beta copied to clipboard
A Flutter package for embedding social media content (X, TikTok, Instagram, Facebook, YouTube, Spotify, Vimeo, and more) using the OEmbed protocol with WebView rendering.
flutter_oembed #
A powerful, easy-to-use Flutter package for embedding social media content and other rich media using the oEmbed protocol.
Features #
- Multi-provider Support: X (Twitter), TikTok, Instagram, Facebook, YouTube, Spotify, Vimeo, and more.
- Dynamic Sizing: Automatically adjusts height to fit embedded content.
- Smart Caching: Built-in caching for oEmbed responses to improve performance and respect rate limits.
- Iframe Optimization: Direct iframe rendering support for YouTube and Spotify to skip API round-trips.
- Privacy & Security: Securely handles authentication tokens and implements Content Security Policy (CSP).
- Extensible: Easily add custom providers and render rules.
- Rich Media Support: Embed not just posts, but also videos, music, and more.
Supported Providers #
flutter_oembed supports the following verified oEmbed providers out of the box:
| Category | Providers |
|---|---|
| Social | X (Twitter), Facebook, Instagram, Threads, Reddit, TikTok |
| Video | YouTube, Vimeo, Dailymotion |
| Audio | Spotify, SoundCloud |
Getting started #
Add flutter_oembed to your pubspec.yaml:
dependencies:
flutter_oembed: ^0.0.1
Usage #
Basic Usage #
Wrap your app in an EmbedScope to provide global configuration:
EmbedScope(
config: EmbedConfig(
facebookAppId: 'YOUR_APP_ID',
facebookClientToken: 'YOUR_CLIENT_TOKEN',
),
child: MyApp(),
)
Meta (Facebook / Instagram / Threads) Setup
Facebook, Instagram and Threads embeds require a Meta App ID and Client Token. You can obtain these from the Meta for Developers portal. For more details on the oEmbed API requirements and setup, see:
- Meta oEmbed Documentation
- Facebook oEmbed API Guide
- Instagram oEmbed API Guide
- Threads oEmbed API Guide
Then use the EmbedCard widget anywhere in your app:
EmbedCard(
url: 'https://twitter.com/X/status/1328842765115920384',
embedType: EmbedType.x,
onLinkTap: (url, data) {
print('User tapped link: $url');
},
)
Advanced Configuration #
You can customize caching, render modes, and styles:
EmbedConfig(
providers: EmbedProviderConfig(
providerRenderModes: {
'YouTube': EmbedRenderMode.iframe,
'Spotify': EmbedRenderMode.iframe,
},
),
cache: EmbedCacheConfig(
enabled: true,
defaultCacheDuration: Duration(days: 7),
),
style: EmbedStyle(
borderRadius: BorderRadius.circular(12),
),
)
Debug Logging #
Enable debug logging when you want to trace provider resolution, cache hits, network requests, and WebView loading events:
EmbedConfig(
logger: const EmbedLogger.debug(),
)
You can also forward logs to your own logger:
EmbedConfig(
onLinkTap: (url, data) {
debugPrint('Clicked $url on $data');
},
logger: EmbedLogger.enabled(
level: EmbedLogLevel.info,
sink: ({
required EmbedLogLevel level,
required String message,
Object? error,
StackTrace? stackTrace,
}) {
myLogger.log(
message,
level: level.name,
error: error,
stackTrace: stackTrace,
);
},
),
)
Markdown Integration (markdown_widget) #
markdown_widget does not parse HTML tags by default, so add a custom block
syntax for <oembed> and a tag generator:
class EmbedBlockSyntax extends md.BlockSyntax {
const EmbedBlockSyntax();
@override
RegExp get pattern => RegExp(
r'^\s*<oembed\b[^>]*>(?:.*</oembed>\s*)?$|^\s*<oembed\b[^>]*/>\s*$',
caseSensitive: false,
);
@override
md.Node? parse(md.BlockParser parser) {
final raw = parser.current.content.trim();
parser.advance();
final url = RegExp(r'\burl\s*=\s*"([^"]+)"', caseSensitive: false)
.firstMatch(raw)
?.group(1) ??
RegExp(r'<oembed\b[^>]*>([\s\S]*?)</oembed>', caseSensitive: false)
.firstMatch(raw)
?.group(1)
?.trim();
if (url == null || url.isEmpty) return md.Text(raw);
final element = md.Element('oembed', [md.Text(url)]);
element.attributes['url'] = url;
return element;
}
}
MarkdownWidget(
data: content,
markdownGenerator: MarkdownGenerator(
blockSyntaxList: const [EmbedBlockSyntax()],
generators: [
SpanNodeGeneratorWithTag(
tag: 'oembed',
generator: (e, config, visitor) => EmbedNode(
e.attributes['url'] ?? e.textContent,
),
),
],
),
)
class EmbedNode extends SpanNode {
final String url;
EmbedNode(this.url);
@override
InlineSpan build() {
return WidgetSpan(
child: EmbedCard(
url: url,
),
);
}
}
HTML Integration (flutter_html) #
Use a TagExtension and extract URL from url, href, src, data-url, or
inner text:
String? extractEmbedUrl(ExtensionContext context) {
final attrs = context.attributes;
final candidates = [
attrs['url'],
attrs['href'],
attrs['src'],
attrs['data-url'],
context.innerHtml.trim(),
];
for (final c in candidates) {
final uri = Uri.tryParse(c ?? '');
if (uri != null && (uri.scheme == 'http' || uri.scheme == 'https')) {
return c!.trim();
}
}
return null;
}
Html(
data: htmlContent,
extensions: [
TagExtension(
tagsToExtend: {"oembed"},
builder: (context) {
final url = extractEmbedUrl(context);
if (url == null) return const SizedBox.shrink();
return EmbedCard(
url: url,
);
},
),
],
)
Additional information #
For more examples, check the /example folder in the repository.