I am trying to create a Xamarin Forms application that displays a full screen video for the launch page. Once loading is complete the launch page takes you to the home screen. The home screen displays a series of videos in a carousel.
On Android the carousel videos work fine by themselves but when I add in the launch screen video it displays over the carousel ones. Currently I have a View called VideoView in my App that has a BindableProperty for the video source.
The rendering is done platform specifically in a custom renderer that is below. It uses a native VideoView and MediaPlayer control for Android. I have tried the various dispose and and release functions in the OnDisappearing function in the launch screen, but none of them seem to release the video.
[assembly: ExportRenderer(typeof(App.Renderers.VideoView),
typeof(VideoViewRenderer))]
namespace App.Droid.Renderers
{
public class VideoViewRenderer :
ViewRenderer<App.Renderers.VideoView, VideoView>, ISurfaceHolderCallback
{
private VideoView videoview;
private MediaPlayer player;
public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Format format, int width, int height)
{
}
public void SurfaceDestroyed(ISurfaceHolder holder)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<App.Renderers.VideoView> e)
{
base.OnElementChanged(e);
e.NewElement.StopAction = () =>
{
player.Pause();
};
e.NewElement.StartAction = () =>
{
player.Start();
};
e.NewElement.DisposeAction = () =>
{
Control.StopPlayback();
Control.Dispose();
player.Stop();
player.Reset();
player.Release();
};
if (Control == null)
{
videoview = new VideoView(Context);
base.SetNativeControl(videoview);
Control.Holder.AddCallback(this);
player = new MediaPlayer();
play(e.NewElement.FileSource);
}
}
void play(string fullPath)
{
AssetFileDescriptor afd = Forms.Context.Assets.OpenFd(fullPath);
if (afd != null)
{
try
{
player.SetDataSource(afd.FileDescriptor, afd.StartOffset, afd.Length);
player.Prepare();
player.Looping = true;
Control.Layout(0, 200, player.VideoHeight, player.VideoWidth);
player.SetVideoScalingMode(VideoScalingMode.ScaleToFit);
}
catch
{
}
}
}
public void SurfaceCreated(ISurfaceHolder holder)
{
player.SetDisplay(holder);
}
}
}
The VideoView class extends View and is as follows:
public class VideoView : View
{
public Action StopAction;
public Action StartAction;
public Action DisposeAction;
public VideoView()
{
FileSource = string.Empty;
}
public static readonly BindableProperty FileSourceProperty =
BindableProperty.Create<VideoView, string>(
p => p.FileSource, string.Empty);
public string FileSource
{
get { return (string)GetValue(FileSourceProperty); }
set { SetValue(FileSourceProperty, value); }
}
public void Stop()
{
if (StopAction != null)
StopAction();
}
public void Dispose()
{
if (DisposeAction != null)
DisposeAction();
}
public void Start()
{
if (StartAction != null)
StartAction();
}
}
And finally usage of the view is as follows:
<controls:VideoView Grid.Row="0" Grid.Column="0" x:Name="Video1" FileSource="big_buck_bunny.mp4" Margin="0,10,0,40" />
Related
I am currently migrating my Xamarin.Forms app to .NET MAUI, and having a difficulty in migrating view renderer. In .NET MAUI I am using camera2 in my app, and using the renderer for same.
My Xamarin forms code is
public class CameraRecordV3 : View
{
public static readonly BindableProperty StartProperty = BindableProperty.Create(
"Start", typeof(int), typeof(int), 6000);
public int Start
{
set { SetValue(StartProperty, value); }
get { return (int)GetValue(StartProperty); }
}
}
using iVue.Views;
using System.ComponentModel;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.Handlers.Compatibility;
namespace iVue.Platforms.Android.Renderers;
public class CameraRecordRenderer_V3 : ViewRenderer<CameraRecordV3, CameraRecordControl_V3>
{
private CameraRecordControl_V3 _cameraControl;
private DisplayTimeHelper _displayTimeHelper = new DisplayTimeHelper();
public CameraRecordRenderer_V3(Context context)
: base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<CameraRecordV3> e)
{
base.OnElementChanged(e);
if (Control == null)
{
_cameraControl = new CameraRecordControl_V3(Context, e.NewElement);
SetNativeControl(_cameraControl);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var model = (CameraRecordV3)sender;
base.OnElementPropertyChanged(sender, e);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_cameraControl.Dispose();
if(Control != null)
Control.Dispose();
}
}
}
CameraRecordControl_V3 is a viewgroup which contains a native view for android, which contains buttons and camera
public class CameraRecordControl_V3 : ViewGroup
{
public CameraRecordControl_V3(Context context, CameraRecordV3 vm) : base(context)
{
_activity = this.Context as Activity;
_view = _activity.LayoutInflater.Inflate(Resource.Layout.CameraRecordLayoutV2, this, false);
AddView(_view);
_toolbar = (Toolbar)_view.FindViewById(Resource.Id.toolbar);
textureView = (AutoFitTextureView)_view.FindViewById(Resource.Id.textureview)
_questionTitleView = (Button)_view.FindViewById(Resource.Id.Start);
}
}
I tried using handler in .net maui but no luck with it.
My Maui Code is as follows
public interface ICameraRecordV3 : IView
{
public int StartTime { get; }
}
public partial class CameraRecordV3Handler
{
public static PropertyMapper<ICameraRecordV3, CameraRecordV3Handler> CustomMapper
= new PropertyMapper<ICameraRecordV3, CameraRecordV3Handler>(ViewHandler.ViewMapper)
{
[nameof(ICameraRecordV3.StartTime)] = MapStartTime,
};
public CameraRecordV3Handler() : base(CustomMapper)
{
}
public CameraRecordV3Handler(PropertyMapper mapper = null) : base(mapper ?? CustomMapper)
{
}
}
public class CameraRecordV3 : View, ICameraRecordV3
{
public static readonly BindableProperty StartProperty = BindableProperty.Create(
"StartTime", typeof(int), typeof(int), 6000);
public int Start
{
set { SetValue(StartTimeProperty, value); }
get { return (int)GetValue(StartTimeProperty); }
}
}
//Platform Specific code
public partial class CameraRecordV3Handler : ViewHandler<ICameraRecordV3, CameraRecordControl_V3>
{
private CameraRecordControl_V3 _cameraControl;
protected override CameraRecordControl_V3 CreatePlatformView()
{
_cameraControl = new CameraRecordControl_V3(Context, null);
return _cameraControl;
}
protected override void ConnectHandler(CameraRecordControl_V3 platformView)
{
base.ConnectHandler(platformView);
}
private static void MapStartTime(CameraRecordV3Handler handler, ICameraRecordV3 arg2)
{
handler.PlatformView?.UpdateStartTime(arg2.StartTime);
}
}
//MauiProgram
builder.ConfigureMauiHandlers(handlers =>
{
#if __ANDROID__
handlers.AddHandler(typeof(CameraRecordV3), typeof(iVue.Handlers.CameraRecordV3Handler));
#endif
});
You can continue to use CustomRenderer in MAUI, you just need to Remove any ExportRenderer directives as they won't be needed in .NET MAUI. And then configure each renderer using conditional compilation for each platform. You can replace handlers.AddCompatibilityRenderer with handlers.AddHandler in the documentation. Using handlers.AddCompatibilityRenderer will cause a crash.
Using an Android renderer for a Frame inside a page in Xamarin Forms, I need to change the position of this object after the size allocation of the page.
The page being in a tab in a Shell, when I change tabs and I come back I get the exception 'Cannot access a disposed object' in the renderer.
The exception occurs on this line of UpdatePos:
SetY(20);
My problem has been reproduced with the code below :
The page :
public partial class TestPage : ContentPage
{
public partial class Container : Frame
{
public delegate void PosChangedEvent();
public event PosChangedEvent HandlerPosUpdated;
public void Update()
{
HandlerPosUpdated?.Invoke();
}
}
Container _container = null;
public TestPage()
{
InitializeComponent();
_container = new Container()
{
Content = new myView()
};
main_layout.Children.Add(_container);
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
_container.Update();
}
}
The renderer :
public class ContainerRenderer : ViewRenderer<Frame, Android.Views.View>
{
public ContainerRenderer(Context context) : base(context)
{}
public void UpdatePos()
{
SetY(20); // System.ObjectDisposedException: 'Cannot access a disposed object. Object name: 'ContainerRenderer'.'
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
TestPage.Container view = e.NewElement as TestPage.Container;
if (view != null)
{
view.HandlerPosUpdated += UpdatePos;
}
}
if (e.OldElement != null)
{
TestPage.Container view = e.OldElement as TestPage.Container;
if (view != null)
{
view.HandlerPosUpdated -= UpdatePos;
}
}
}
}
How this exception could be avoided ?
Any hints are welcome!
Remove that handler when the custom renderer is disposed:
private bool disposedValue;
protected override void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
RemoveHandlerPosUpdated();
}
disposedValue = true;
}
base.Dispose(disposing);
}
private void RemoveHandlerPosUpdated()
{
if (Element != null)
{
TestPage.Container view = Element as TestPage.Container;
if (view != null)
{
view.HandlerPosUpdated -= UpdatePos;
}
}
}
If that doesn't fix it, then may need to do something in TestPage.Container class, to remove any handlers attached to HandlerPosUpdated. Details TBD.
I am making a Flappy Bird game and trying to display ads when the Bird dies.
I have setup Google AdMob and UnityAds from Google AdMob Console and Unity Dashboard respectively.
This is my code below.
using GoogleMobileAds.Api;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class GameController : MonoBehaviour {
public static GameController instance;
public GameObject gameOverText;
public bool isGameOver = false;
public float scrollSpeed = -1.5f;
public Text scoreText;
private int score = 0;
private InterstitialAd interstitialAd;
// Awake is called before Start
void Awake() {
if(instance == null) {
instance = this;
} else if (instance != null) {
Destroy(gameObject);
}
}
// Start is called before the first frame update
void Start() {
MobileAds.Initialize(initStatus => { });
RequestInterstitial();
}
private void RequestInterstitial() {
string adUnitId = "unexpected_platform";
#if UNITY_ANDROID
adUnitId = Values.TEST_ANDROID_AD_UNIT_ID;
#endif
// Initialize InterstitialAd
this.interstitialAd = new InterstitialAd(adUnitId);
// Create empty ad request
AdRequest request = new AdRequest.Builder().Build();
// Load interstitial with the request
this.interstitialAd.LoadAd(request);
}
public void ShowAd() {
if (this.interstitialAd.IsLoaded()) {
this.interstitialAd.Show();
}
}
// Update is called once per frame
void Update() {
if(isGameOver && Input.GetKey(KeyCode.Space)) {
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
} else if (isGameOver && Input.touchCount > 0) {
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
public void BirdScored() {
if(isGameOver) {
return;
}
score++;
scoreText.text = "Score: " + score;
}
public void BirdDied() {
gameOverText.SetActive(true);
isGameOver = true;
ShowAd();
}
}
The code reaches the ShowAd() function when I debug but the ad is not displayed.
What am I doing wrong?
I am implementing Google Cast for my video app (min API 16) with live HLS streams. I follow the instructions https://codelabs.developers.google.com/codelabs/cast-videos-android/#5 and stopped in step 6. When I tap on cast button, Cast dialog appears, I select my Android TV device (test device Mibox 3, Android 6), remote player is trying to load video, but instead of HLS live video I see a big red title SAMPLE on TV. I decided to play sample video http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8 and again I see SAMPLE. If I tap on cast button again, dialog indicates: no media file was chosen. Locally I use ExoPlayer to play HLS live video without problems. I tried MediaInfo.STREAM_TYPE_BUFFERED and MediaInfo.STREAM_TYPE_LIVE
Code:
private void setupCastListener() {
mSessionManagerListener = new SessionManagerListener<CastSession>() {
#Override
public void onSessionEnded(CastSession session, int error) {
onApplicationDisconnected();
}
#Override
public void onSessionResumed(CastSession session, boolean wasSuspended) {
onApplicationConnected(session);
}
#Override
public void onSessionResumeFailed(CastSession session, int error) {
onApplicationDisconnected();
}
#Override
public void onSessionStarted(CastSession session, String sessionId) {
onApplicationConnected(session);
}
#Override
public void onSessionStartFailed(CastSession session, int error) {
onApplicationDisconnected();
}
#Override
public void onSessionStarting(CastSession session) {}
#Override
public void onSessionEnding(CastSession session) {}
#Override
public void onSessionResuming(CastSession session, String sessionId) {}
#Override
public void onSessionSuspended(CastSession session, int reason) {}
private void onApplicationConnected(CastSession castSession) {
mCastSession = castSession;
if (clickedChannel != null) {
if (isPlaying()) {
loadRemoteMedia(true);
}
}
}
private void onApplicationDisconnected() {
}
};
}
private void loadRemoteMedia(boolean autoPlay) {
if (mCastSession == null) {
return;
}
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
if (remoteMediaClient == null) {
return;
}
remoteMediaClient.load(buildMediaInfo(), autoPlay);
}
private MediaInfo buildMediaInfo() {
MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
mediaMetadata.putString(MediaMetadata.KEY_TITLE, clickedChannel.getName());
selectedStreamingURL = "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8";
return new MediaInfo.Builder(selectedStreamingURL)
.setContentType("application/x-mpegURL")
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setMetadata(mediaMetadata)
.build();
}
Problem was solved by changing this method
#Override
public CastOptions getCastOptions(Context context) {
return new CastOptions.Builder()
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.build();
}
I am new so excuse me if I dont ask a question right or post enough information.
I am new to creating mobile applications and i am using Xamarin.Forms to create a custom view. with this view I am using an Android ViewRenderer to play audio/video with built in android MediaPlayer/VideoView.
pretty much the exact same thing as the android renderer posted and accepted as the answer for Renderer I copied and is working
My issue is when the video starts and you click the homepage/back button the audio continues playing for a few seconds and then stops. I want to audio to stop immediately.
Methods I have tried:
In my ViewRenderer I have attempted to override SurfaceDestroyed to call player.stop(). This has not worked, no errors or anything just audio continues like this code doesnt exist. Audio stops after about 3-5 seconds.
In the ViewRenderer I have attempted to use the Control.SystemUiVisibilityChange event to call player.stop(). No errors or anything. Audio continues for 3-5 seconds.
I am unable to pass the player instance to the main activity onPause() method as I am to new to Xamarin.Forms and android ViewRenderers to understand how to. Possibly calling this player.stop() on the onPause() method will work but I cant find how to do this. Can anyone assist? I have searched many forums for weeks and have finally given up to post a question.
For back button, you simply need to override OnBackButtonPressed of your current Xamarin.Forms' page:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override bool OnBackButtonPressed()
{
//stop the videoview
videoview.Stop();
return base.OnBackButtonPressed();
}
...
}
For home button, I referred to this thread and made a Xamarin version of HomeWatcher out of Jack's answer:
public interface IOnHomePressedListener
{
void OnHomePressed();
void OnHomeLongPressed();
}
public class HomeWatcher
{
static readonly String TAG = "hg";
private Context mContext;
private IntentFilter mFilter;
private IOnHomePressedListener mListener;
private InnerRecevier mRecevier;
public HomeWatcher(Context context)
{
mContext = context;
mFilter = new IntentFilter(Intent.ActionCloseSystemDialogs);
}
public void SetOnHomePressedListener(IOnHomePressedListener listener)
{
mListener = listener;
mRecevier = new InnerRecevier(mListener);
}
public void StartWatch()
{
if (mRecevier != null)
{
mContext.RegisterReceiver(mRecevier, mFilter);
}
}
public void StopWatch()
{
if (mRecevier != null)
{
mContext.UnregisterReceiver(mRecevier);
}
}
private class InnerRecevier : BroadcastReceiver
{
readonly String SYSTEM_DIALOG_REASON_KEY = "reason";
readonly String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
readonly String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
readonly String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
IOnHomePressedListener _listener;
public InnerRecevier(IOnHomePressedListener listener)
{
_listener = listener;
}
public override void OnReceive(Context context, Intent intent)
{
String action = intent.Action;
if (action.Equals(Intent.ActionCloseSystemDialogs))
{
String reason = intent.GetStringExtra(SYSTEM_DIALOG_REASON_KEY);
if (reason != null)
{
//Log.e(TAG, "action:" + action + ",reason:" + reason);
if (_listener != null)
{
if (reason.Equals(SYSTEM_DIALOG_REASON_HOME_KEY))
{
_listener.OnHomePressed();
}
else if (reason.Equals(SYSTEM_DIALOG_REASON_RECENT_APPS))
{
_listener.OnHomeLongPressed();
}
}
}
}
}
}
}
And use it in the VideoViewRenderer ( StartWatch() when video start play, StopWatch() when the videoview is cleaned):
public class VideoViewRenderer : ViewRenderer<VideoView, Android.Widget.VideoView>, ISurfaceHolderCallback,IOnHomePressedListener
{
...
private MediaPlayer _player;
private HomeWatcher _homeWatcher;
public VideoViewRenderer(Context context) : base(context)
{
_context = context;
_homeWatcher = new HomeWatcher(context);
_homeWatcher.SetOnHomePressedListener(this);
}
protected override void OnElementChanged(ElementChangedEventArgs<CustomVideoViewDemo.VideoView> e)
{
base.OnElementChanged(e);
e.NewElement.CleanAction = new Action(() =>
{
#region Clean video player action (player no more used)
if (_player == null)
return;
//stop watch home button
_homeWatcher.StopWatch();
_player.Release();
#endregion
});
e.NewElement.PlayAction = new Action(() =>
{
#region Play video if it was stopped
if (_player == null)
return;
//start watch home button
_homeWatcher.StartWatch();
if (!_player.IsPlaying)
{
_player.Start();
}
#endregion
});
...
}
}