Player Integration in Flutter WebView
Overview
In this document, we briefly go through the steps to get started with integrating the LORA Live Video Shopping player into your Flutter app.
Requirements
Your app supports WebViews (or is web-based)
A webpage embedding LORA Live Video Shopping player, which can be loaded inside the WebView
Hosted and managed by you
Includes JavaScript code for connecting the player with your app
Below we explain how to integrate the LORA Live Video Shopping player in your Flutter app. Note that the examples we use are from the sample project on GitHub.
Getting started
Here you find a simple example project that can help you understand the implementation and get started with the technical integration quickly.
Embed HTML page
- An static HTML page which we render inside the webview
- Player configuration and event listeners
- Sample show embedded
How it works
Because the LORA Live Video Shopping player is a web app, it works perfectly within a webview. Thanks to the ability mobile platforms to communicate between WebView and the flutter code, it is possible to utilize the LORA Player JavaScript API to configure and customize the behavior of the player inside the WebView.
Create an HTML page
Steps:Setup a webpage to render inside the webview
- Hosted and managed on the customer side
- Recommended to be hosted remotely
Embed the player on this webpage (Learn more)
Setup a WebView
Steps:- Create a new class
WebViewScreen
- Set
allowsInlineMediaPlayback
andmediaTypesRequiringUserAction
for iOS orsetMediaPlaybackRequiresUserGesture
for Android (Necessary to render LORA Live Video Shopping player)
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
class WebViewScreen extends StatefulWidget {
const WebViewScreen({super.key});
State<WebViewScreen> createState() => _WebViewScreenState();
}
class _WebViewScreenState extends State<WebViewScreen> {
late final WebViewController controller;
void initState() {
super.initState();
controller = createWebViewController();
controller.loadRequest(Uri.parse('https://sdk-docs.belive.technology/lora-webview-embed/index.html'));
}
WebViewController createWebViewController() {
late final PlatformWebViewControllerCreationParams params;
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
params = WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
} else {
params = const PlatformWebViewControllerCreationParams();
}
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
if (controller.platform is AndroidWebViewController) {
AndroidWebViewController.enableDebugging(true);
(controller.platform as AndroidWebViewController)
.setMediaPlaybackRequiresUserGesture(false);
}
return controller;
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: WebViewWidget(
controller: controller,
),
),
);
}
}
- Initiate the view
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: WebViewScreen(),
);
}
}
Establish a bridge between flutter app and webview
There is a need to set up a data flow from the flutter app to the webview and vice versa.
Communicate from Flutter App to Webview
Evaluate Javascript inside the webview
Here we set up an interface to execute javascript code inside the webview. In the example below, we create a JSON that contains some configuration data. Then we construct a javascript code as a string to attach the configuration data to the window object as window.appConfig
. In webview_screen.dart
, add the following:
class _WebViewScreenState extends State<WebViewScreen> {
...
WebViewController createWebViewController() {
late final PlatformWebViewControllerCreationParams params;
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
params = WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
} else {
params = const PlatformWebViewControllerCreationParams();
}
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(NavigationDelegate(
onPageStarted: (String url) {
runJavaScript("window.appConfig = {'foo': 'bar'}");
},
));
if (controller.platform is AndroidWebViewController) {
AndroidWebViewController.enableDebugging(true);
(controller.platform as AndroidWebViewController)
.setMediaPlaybackRequiresUserGesture(false);
}
return controller;
}
Future<void> runJavaScript(String javascript) {
return controller.runJavaScript(javascript);
}
}
On the landing page, where you configure the player, you can use window.appConfig
to initialize configurations
<script>
window.addEventListener("load", function () {
console.log(window.appConfig); // { 'foo': 'bar' }
});
</script>
Communicate from Webview to Flutter App
Steps:
- Listen to messages coming from the webview
- Identify event
- Execute an intended flutter handler for that event
- Add a
JavaScriptChannel
and name itLoraChannel
to establish a communication interface with the webview
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(NavigationDelegate(
onPageStarted: (String url) {
runJavaScript("window.appConfig = {'foo': 'bar'}");
},
))
..addJavaScriptChannel(
'LoraChannel',
onMessageReceived: (JavaScriptMessage message) {
debugPrint(message.message);
},
);
- Sending the event to the app message handler
For communicating a player event from the WebView to the app, we need to create a postMessage
. Then on the app side, we catch the postMessage
and run the intended handler for each event.
<script>
window.addEventListener("load", function () {
var showId = "INPUT_YOUR_SHOW_ID";
window.player = window.BeLivePlayerWidget.initialize({
showId: showId,
navigationMode: window.BeLivePlayerWidget.NavigationMode.MANUAL,
buttons: {
dismiss: window.BeLivePlayerWidget.Button.CLOSE,
},
});
if (LoraChannel) {
LoraChannel.postMessage(
JSON.stringify({
eventName: "player.INITIALIZE",
}),
);
}
});
</script>
- Catching the messages on the app side and based on the event name, we run the desired native functionality.
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(NavigationDelegate(
onPageStarted: (String url) {
runJavaScript("window.appConfig = {'foo': 'bar'}");
},
))
..addJavaScriptChannel(
'LoraChannel',
onMessageReceived: (JavaScriptMessage message) {
Map<String, dynamic> jsonMap = json.decode(message.message);
if (!jsonMap.containsKey('eventName')) return;
switch (jsonMap['eventName'] as String) {
case 'player.INITIALIZE':
// This line will open the player in the webview
runJavaScript('window.player.open("${widget.showId}")');
break;
}
},
);
- You can access the player instance in the WebView by calling
window.player
and call other functions in the same way. Check the Player API for more information.
Handle Player Events
On the landing page, you can use the player API to handle different player events. Below we gathered the most common scenarios that are handled within the context of app integration.
Handle Closing Player
Event name: BeLivePlayerWidget.PlayerEventType.CLOSE
- When a shopper closes the player.
- Make sure to configure the dismiss button as below
window.player = window.BeLivePlayerWidget.initialize({
buttons: {
dismiss: window.BeLivePlayerWidget.Button.CLOSE,
},
});
- Handle
BeLivePlayerWidget.PlayerEventType.CLOSE
to navigate shopper back to your native app
- HTML Embed
- Flutter
window.player.on(window.BeLivePlayerWidget.PlayerEventType.CLOSE, () => {
LoraChannel.postMessage(
JSON.stringify({
eventName: "player.CLOSE"
})
);
});
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'LoraChannel',
onMessageReceived: (JavaScriptMessage message) {
Map<String, dynamic> jsonMap = json.decode(message.message);
if (!jsonMap.containsKey('eventName')) return;
switch (jsonMap['eventName'] as String) {
//...other events
case 'player.CLOSE':
break;
}
},
);
Handle Product View
Event name: BeLivePlayerWidget.PlayerEventType.SHOW_PRODUCT_VIEW
- When a shopper clicks on a product (whether from the product list or featured product).
- Override default product click behavior
window.player = window.BeLivePlayerWidget.initialize({
buttons: {
product: window.BeLivePlayerWidget.Button.NONE,
},
});
- Handle
BeLivePlayerWidget.PlayerEventType.SHOW_PRODUCT_VIEW
event to display your native screen
- HTML Embed
- Flutter
window.player.on(window.BeLivePlayerWidget.PlayerEventType.SHOW_PRODUCT_VIEW, (payload) => {
LoraChannel.postMessage(
JSON.stringify({
eventName: "player.SHOW_PRODUCT_VIEW",
payload
})
);
});
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'LoraChannel',
onMessageReceived: (JavaScriptMessage message) {
Map<String, dynamic> jsonMap = json.decode(message.message);
if (!jsonMap.containsKey('eventName')) return;
switch (jsonMap['eventName'] as String) {
//...other events
case 'player.SHOW_PRODUCT_VIEW':
break;
}
},
);
Handle Share
Event name: BeLivePlayerWidget.PlayerEventType.SHOW_SHARE_VIEW
- When a shopper clicks on share button inside the player
- Hide player's default share view
window.player = window.BeLivePlayerWidget.initialize({
ui: {
hideShareView: true,
},
});
- Handle
BeLivePlayerWidget.PlayerEventType.SHOW_SHARE_VIEW
event to display your native screen
- HTML Embed
- Flutter
window.player.on(window.BeLivePlayerWidget.PlayerEventType.SHOW_SHARE_VIEW, (payload) => {
LoraChannel.postMessage(
JSON.stringify({
eventName: "player.SHOW_SHARE_VIEW",
payload
})
);
});
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'LoraChannel',
onMessageReceived: (JavaScriptMessage message) {
Map<String, dynamic> jsonMap = json.decode(message.message);
if (!jsonMap.containsKey('eventName')) return;
switch (jsonMap['eventName'] as String) {
//...other events
case 'player.SHOW_SHARE_VIEW':
break;
}
},
);
Handle Minimize View
Event name: BeLivePlayerWidget.PlayerEventType.MINIMIZED
- When a shopper clicks on minimize button inside the player or by calling function
minimize()
. Learn more
controller.runJavaScript('window.player.minimize()'),
- Hide player's default minimize action
window.player = window.BeLivePlayerWidget.initialize({
buttons: {
minimize: window.BeLivePlayerWidget.Button.NONE,
},
});
- Handle
BeLivePlayerWidget.PlayerEventType.MINIMIZED
event
- HTML Embed
- Flutter
window.player.on(window.BeLivePlayerWidget.PlayerEventType.MINIMIZED, () => {
LoraChannel.postMessage(
JSON.stringify({
eventName: "player.MINIMIZED"
})
);
});
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'LoraChannel',
onMessageReceived: (JavaScriptMessage message) {
Map<String, dynamic> jsonMap = json.decode(message.message);
if (!jsonMap.containsKey('eventName')) return;
switch (jsonMap['eventName'] as String) {
//...other events
case 'player.MINIMIZED':
break;
}
},
);
Handle Unminimize View
Event name: BeLivePlayerWidget.PlayerEventType.UNMINIMIZED
- When call function
unminimize()
. Learn more
controller.runJavaScript('window.player.unminimize()')
- Hide player's default minimize action
window.player = window.BeLivePlayerWidget.initialize({
buttons: {
minimize: window.BeLivePlayerWidget.Button.NONE,
},
});
- Handle
BeLivePlayerWidget.PlayerEventType.UNMINIMIZED
event
- HTML Embed
- Flutter
window.player.on(window.BeLivePlayerWidget.PlayerEventType.UNMINIMIZED, () => {
LoraChannel.postMessage(
JSON.stringify({
eventName: "player.UNMINIMIZED"
})
);
});
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'LoraChannel',
onMessageReceived: (JavaScriptMessage message) {
Map<String, dynamic> jsonMap = json.decode(message.message);
if (!jsonMap.containsKey('eventName')) return;
switch (jsonMap['eventName'] as String) {
//...other events
case 'player.MINIMIZED':
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: const Duration(seconds: 10),
content: const Text('Player MINIMIZED'),
action: SnackBarAction(
label: 'Undo',
onPressed: () =>
controller.runJavaScript('window.player.unminimize()'),
),
),
);
break;
case 'player.UNMINIMIZED':
break;
}
},
);