I'm making an application which plays lots of videos in a row. However it seems like whenever I hit 60 videos (sometimes less, sometimes a bit more) my app hangs. I made a small sample app which also does this so to me it seems like the problem is within MediaElement control or somewhere deeper.
Here is the code for sample app:
MainPage.xaml:
<Page
x:Class="App2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<MediaElement x:Name="player" AutoPlay="False" MediaOpened="player_MediaOpened" MediaEnded="player_MediaEnded" Volume="0.01" />
<TextBlock x:Name="txtDebug" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="40" FontSize="20" Text="Videos played: 0" Foreground="Yellow" />
<Button Content="Test" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="40,100,0,0" Click="Button_Click" />
</Grid>
</Page>
Code behind:
namespace App2
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
await SetPlayerVideo();
}
private async System.Threading.Tasks.Task SetPlayerVideo()
{
// get next video file, open it and set it to MediaElement.
var nextVideo = await this.SelectNextVideo();
var videoStream = await nextVideo.OpenAsync(Windows.Storage.FileAccessMode.Read);
this.player.SetSource(videoStream, "");
}
private void player_MediaOpened(object sender, RoutedEventArgs e)
{
// When MediaElement.SetSource finishes, begin play.
MediaElement elem = sender as MediaElement;
elem.Play();
}
private async void player_MediaEnded(object sender, RoutedEventArgs e)
{
// Update debug text and start next video when one ends.
this.txtDebug.Text = string.Format("Videos played: {0}", this.videoIndex);
await SetPlayerVideo();
}
private int videoIndex = 0; // Index used to loop through files in temp folder in case there are multiple video files
private async System.Threading.Tasks.Task<Windows.Storage.StorageFile> SelectNextVideo()
{
var files = await Windows.Storage.ApplicationData.Current.TemporaryFolder.GetFilesAsync();
var file = files.ElementAt(this.videoIndex % files.Count);
videoIndex++;
return file;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
txtDebug.Text = "button click";
}
}
}
To test put one wmv file into TempState folder. Preferably short video as it requires to run tens of times to reproduce issue. I used a countdown clip from this site: http://www.movietools.info/video-background-loops/countdown-loops.html
I downloaded some applications from Windows Store with playlist feature, added about 100x 10 second clips and hit play. They all had the same problem, hanged after about 60 videos were played.
Any suggestions or ideas how to get around the issue? I have also tried creating new MediaElement each time video ends but it didn't help.
Next I'll probably research VLC player a bit since they don't seem to use default MediaElement for playing videos. But I haven't really checked if their license allows the use of their dlls in my app or if I can even compile the sources.
The problem seemed to be related to AMD graphics card and seems to be fixed after updating drivers to latest version.
Related
I am trying to make a small music player for windows phone. I have added a slider functionality in the player. The slider works fine as the music plays. But I want to change the media according to how much i drag the slider, but cannot find any relevant event for it. I have tried value changed but it does not help. Also I tried Thumb.Dragstarted event but my visual studio gives an error.. this is the code so far:
XAML:
<Slider AllowDrop="True" x:Name="sld1" Thumb.DragStarted="sld1_DragStarted" HorizontalAlignment="Left" Margin="58,213,0,0" VerticalAlignment="Top" Width="351" ValueChanged="sld1_ValueChanged"/>
<MediaElement x:Name="bleep" Source="abcd.wav" AutoPlay="False" Visibility="Collapsed" MediaEnded="bleep_MediaEnded"/>
C#:
public Page1()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timer_Tick;
timer.Start();
}
private bool userIsDraggingSlider = false;
private void timer_Tick(object sender, EventArgs e)
{
if ((bleep.Source != null) && (bleep.NaturalDuration.HasTimeSpan) && (!userIsDraggingSlider))
{
sld1.Minimum = 0;
sld1.Maximum = bleep.NaturalDuration.TimeSpan.TotalSeconds;
sld1.Value = bleep.Position.TotalSeconds;
}
}
private void sld1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
tm_passed.Text = TimeSpan.FromSeconds(sld1.Value).ToString(#"mm\:ss");
}
private void sld1_DragStarted(object sender, DragStartedEventArgs e)
{
userIsDraggingSlider = true;
}
private void sld1_DragCompleted(object sender, DragCompletedEventArgs e)
{
userIsDraggingSlider = false;
bleep.Position = TimeSpan.FromSeconds(sld1.Value);
}
But since the DragCompleted and DragStarted events are not working I cannot provide the drag functionality to the slider.
What I identified from the Thumb class is that, you can't simply add Thumb.DragStarted="sld1_DragStarted within your Slider. You'll be able to add that kind of event only for Thumb control. Refer the bottom of the article for sample code.
Say I have a splash screen. This screen is loaded only at the beginning of the game, and afterwards I don't use it.
Is it possible to dispose this screen on demand?
I tried to dispose it right after setting the screen after the splash, and also tried to call dispose() on the hide() method.
Both of the tries rendered in an exception.
How can I dispose this screen on demand? I have there pretty heavy textures, and wanted to free the memory as soon as possible.
Example:
// SplashScreen class
class SplashScreen implements Screen {
private boolean renderingEnabled = true;
private Object lock = new Object();
#Override
public void render(float delta) {
synchronized(lock) {
if (!renderingEnabled) {
return;
}
spriteBatch.begin();
// here render the animations
spriteBatch.end();
}
}
#Override
public void dispose() {
synchronized(lock) {
renderingEnabled = false;
// here come the disposing of SpriteBatch and TextureAtlas
atlas.dispose();
atlas = null;
spriteBatch.dispose();
spriteBatch = null;
}
}
}
// The usage
game.setScreen(splashScreen);
...
// now when the splash screen animation is finished, I am calling the following from the controller:
splashScreen.dispose();
game.setScreen(mainMenuScreen);
I get the following exception:
FATAL EXCEPTION: GLThread 398
java.lang.NullPointerException
at mypackage.SplashScreen.render(SplashScreen.java:85)
at com.badlogic.gdx.Game.render(Game.java:46)
at mypackage.Game.render(MyGame.java:33)
at com.badlogic.gdx.backends.android.AndroidGraphics.onDrawFrame(AndroidGraphics.java:414)
at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1522)
at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1240)
And when I have the following dispose pattern (disposing after showing the mainMenuScreen)
// The usage
game.setScreen(splashScreen);
...
// now when the splash screen animation is finished, I am calling the following from the controller:
game.setScreen(mainMenuScreen);
splashScreen.dispose();
I get the same exception.
The exception is on spriteBatch.end() row inside the render method. The spriteBatch member turns to be null. Really confusing, since I have mutual exclusive lock on the render and the dispose methods.
What I figured out, is that LibGDX works in one thread.
What happens is the following. When the game.setScreen(mainMenuScreen) is called, and the program is in the middle of the render method of the splash screen, the thread is interrupted, and the code for setting the mainMenuScreen is executed (in the Game class), and then going back to continue the render method of the splash screen. Weird, right? After setting the new screen still to render the old screen?
Anyways, what I did is the following:
As part of the Game.setScreen(Screen screen) method, the Screen.hide() method is called on the old screen.
So what I did, I introduced a flag, hideCalled and set it to true in the hide() event inside the splash screen.
Inside the render method of the splash screen I check this flag at the beginning and at the end of the method. If the flag is true - I dispose the screen.
I was just thinking about AssetManager, maybe it would help you with this problem in a cleaner way.
Take a look here.
I see it loads assets asynchronously so maybe this problem that you described would not be happening again.
And just for some knowledge sharing, I changed screens with another approach:
I used actions attached to an image, and these actions are executed in the order they are added. First i created an action which loads the images, then, when this first action was finished, i added another action which switches the screen.
Here is my code:
#Override
public void show() {
stage.addActor(splashImage);
splashImage.addAction(Actions.sequence(Actions.alpha(0), Actions.run(new Runnable() {
#Override
public void run() {
Assets.load();
}
}), Actions.fadeIn(0.5f), Actions.delay(1), Actions.fadeOut(0.5f), Actions.run(new Runnable() {
#Override
public void run() {
Utils.changeGameScreen(new GameScreen());
// ((Game) Gdx.app.getApplicationListener()).setScreen(new
// GameScreen());
}
})));
}
I am working on a Windows Phone 8 App which should be protected with a passcode. What is the best way to show the passcode screen everytime the app is lauchend or activated?
I think the central point of action shoule be the App.xaml.cs with its Launch and Activation event handlers. But how exactly can I show the passcode screen?
The problem is, that one never know which pages will be displayed when the app launches or is reactivated. It is either the main page or any other page which was last displayed when the app was deactivated.
I tried to intercept the navigation to the first page, cancel it and show the passcode page instead:
// App.xaml.cs
private void InitializePhoneApplication() {
...
RootFrame.Navigating += HandleFirstNavigation;
...
}
private void HandleFirstNavigation(object sender, NavigatingCancelEventArgs e) {
RootFrame.Navigating -= HandleFirstNavigation;
e.Cancel = true;
RootFrame.Dispatcher.BeginInvoke(new Action(this.OpenPasscodePage));
}
private void OpenPasscodePage() {
RootFrame.Navigate(PasscodePageUri);
}
This works, but only when the app lauchend. When the app reactivated (dormant or tombstoned) the e.Cancel is irgnored. Although the navigation to the passcode page is called the original page is shown.
Moving the navigation the the passcode page from Navigating to Navigated does not worth either:
private void InitializePhoneApplication() {
...
RootFrame.Navigated += PasscodePageAfterFirstNavigation;
...
}
private void PasscodePageAfterFirstNavigation(object sender, EventArgs e) {
RootFrame.Navigated-= PasscodePageAfterFirstNavigation;
RootFrame.Navigate(PasscodePageUri);
}
This seems to be some kind of race condition: Sometimes the passcode page is shown, sometimes the original page. Even if the passcode pages comes up this looks bad because one first see the original page for the fraction of a second before the app navigates further to the passcode page.
Both solution do not work. Any idea what is the right way to implement this?
EDIT: Meanwhile I tried a third solution which does not work either. This solution uses the Uri Mapper:
App.xaml.cs
public bool PasscodeWasConfirmed; private void Application_Launching(object sender, LaunchingEventArgs e) {
...
PasscodeWasConfirmed = false;
...
}
private void Application_Activated(object sender, ActivatedEventArgs e) {
...
PasscodeWasConfirmed = false;
...
}
public Uri InitialPageUri;
public bool ShouldRedirectToPasscodePage(Uri uri) {
if (PasswordWasConfirmend == false) {
InitialPageUri = uri;
return true;
}
return false;
}
UriMapper
public class AppUriMapper : UriMapperBase {
public override Uri MapUri(Uri uri) {
App app = (Application.Current as App);
if (app != null) {
if (app.ShouldRedirectToPasscodePage(uri))
return PasscodeQueryPage.PageUri;
}
// default
return uri;
}
}
PasscodePage
public partial class PasscodePage : PhoneApplicationPage {
...
private void PasscodeConfirmed() {
App app = (Application.Current as App);
app.PasscodeWasConfirmed = true;
NavigationService.Navigate(app.InitialPageUri);
}
}
The Logic is working without any problem, but the app does not navigate to InitialPageUri after the passcode was confirmed. The Uri Mapper is called and correctly and returns the InitialPageUri (no redirect any more). But no navigation happens...
There are no errors, exceptions or debug output. simply nothing happes...
Biggest problem when using Uri Mapper:
When the app is reactivated from Dormant state there is no navigation which could be mapped or redirected...
(I've edited previous answer instead of adding a new one)
I've spend a little time trying to find a solution, and I don't see why your code doesn't run.
In my case it's enough if I do such a change in App.xaml:
private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
// Set the root visual to allow the application to render
if (RootVisual != RootFrame)
RootVisual = RootFrame;
// Remove this handler since it is no longer needed
RootFrame.Navigated -= CompleteInitializePhoneApplication;
App.RootFrame.Navigate(new Uri("/passPage.xaml", UriKind.RelativeOrAbsolute));
}
This works on my example which is under the link http://sdrv.ms/1ajH40E
But - I cannot prevent user from seeing last screen when he holds back buton and is chosing to which app return, and then for a blink he can see the last page before leaving the app. I don't know if it is possible to change this behaviour after clicking MS Button:
windows phone change deactivated app image
Second edit
Ok - maybe I've found solution why it sometiems work and sometimes not in your code. After pressing the Start or Search buton the App can go to two cases: Tombstone and non-tombsone. After return different events happen. Code above works with Tombstone case but not with non-tombstone. To work it with the second you need to add (because page is not initialized again) - (of course it can be different solution):
bool afterActivation = false;
private void Application_Activated(object sender, ActivatedEventArgs e)
{
afterActivation = true;
}
private void CheckForResetNavigation(object sender, NavigationEventArgs e)
{
// If the app has received a 'reset' navigation, then we need to check
// on the next navigation to see if the page stack should be reset
if (e.NavigationMode == NavigationMode.Reset)
RootFrame.Navigated += ClearBackStackAfterReset;
if (afterActivation)
{
afterActivation = false;
App.RootFrame.Navigate(new Uri("/passPage.xaml", UriKind.RelativeOrAbsolute));
}
}
Please also ensure of your debug properties in VS: Project->Properties->Debug->Tombstone upon deactiovation checkbox.
You can also find some information here (if you haven't seen it before):
http://blogs.msdn.com/b/ptorr/archive/2010/12/11/how-to-correctly-handle-application-deactivation-and-reactivation.aspx
In my app I need to display a collection of Images exactly like in the Windows Phone 8 Photo App where you can swipe right and left between the images.
I've tried both the Panorama and Pivot control but both controls don't behave like WinRTs FlipView.
Panorama fits quite well but appears to have the "Right-Peek" Amount hardwired into the control. (please correct me if I'm wrong)
Pivot in turn shows blackness during swipes (finger still down) and only displays the next image when you release your finger and the control scrolls the next item into place.
Any suggestions?
Here is the customized FlipView control for WP8 like WINRT FlipView Control...
Step 1 : Add a new Usercontrol and name it as "FlipView.xaml"
Step 2 : Add following xaml in "FlipView.xaml"
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<ContentPresenter Name="contentPresenter"/>
<Button BorderThickness="0" Name="leftButton" FontSize="70" Margin="-25" HorizontalAlignment="Left" VerticalAlignment="Center" Content="<" Click="Button_Click"/>
<Button BorderThickness="0" Name="rightButton" FontSize="70" Margin="-25" HorizontalAlignment="Right" VerticalAlignment="Center" Content=">" Click="Button_Click_1"/>
</Grid>
Step 3 : Add the following code in the "FlipView.cs"
public partial class FlipView : UserControl
{
public FlipView()
{
InitializeComponent();
Datasource = new List<object>();
SelectedIndex = 0;
}
private IList Datasource;
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(FlipView), new PropertyMetadata(default(DataTemplate)));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set
{
SetValue(ItemTemplateProperty, value);
contentPresenter.ContentTemplate = value;
contentPresenter.Content = SelectedItem;
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IList), typeof(FlipView), new PropertyMetadata(default(IList)));
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
Datasource = value;
SelectedIndex = SelectedIndex;
}
}
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register("SelectedIndex", typeof(int), typeof(FlipView), new PropertyMetadata(default(int)));
public int SelectedIndex
{
get { return (int)GetValue(SelectedIndexProperty); }
set
{
SetValue(SelectedIndexProperty, value);
rightButton.Visibility = leftButton.Visibility = Visibility.Visible;
if (SelectedIndex == 0)
{
leftButton.Visibility = Visibility.Collapsed;
}
if (SelectedIndex + 1 == Datasource.Count)
{
rightButton.Visibility = Visibility.Collapsed;
SelectedItem = Datasource[SelectedIndex];
}
if (Datasource.Count > SelectedIndex + 1)
{
SelectedItem = Datasource[SelectedIndex];
}
}
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(FlipView), new PropertyMetadata(default(object)));
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set
{
SetValue(SelectedItemProperty, value);
contentPresenter.Content = SelectedItem;
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SelectedIndex--;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
SelectedIndex++;
}
}
Step 4 : Now at the mainpage, add the namespace to use the flipview Usercontrol
Example:
xmlns:FlipViewControl="clr-namespace:ImageFlip" (Note: It differs according to your Solution name).
Step 5 : Using the namespace, add the flipview control as follow as..
<Grid x:Name="LayoutRoot" Background="Transparent">
<FlipViewControl:FlipView Name="imgViewer">
<FlipViewControl:FlipView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Stretch="Fill"/>
</DataTemplate>
</FlipViewControl:FlipView.ItemTemplate>
</FlipViewControl:FlipView>
</Grid>
Step 6 : Add the following code in mainpage.cs
// Constructor
public MainPage()
{
InitializeComponent();
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
imgViewer.ItemsSource = new List<string> { "/Images/1.jpg", "/Images/2.jpg", "/Images/3.jpg" };
}
Hope this will help.
Thanks
There is no direct equivalent to the FlipView in Windows Phone. The Panorama and Pivot controls have very different functionalities and are designed fro different purposes.
Telerik have a SlideView control which is very similar to the native control used by the photos app.
You can also get the Telerik controls free as part of the Nokia Premium Developer Program. (Worth investigating if you don't have a Dev Center subscription.)
I know it is not the same solution, but maybe you can tweak this coverflow example here... so that the images are not stacked but side by side?
I am using a videoview with an animation (video.setAnimation(slideinRight);) Everything works ok except that on the transition, only the layout of the videview is animating, not the video. When the translate animation occurs, i see a box move and mask over my video but the video never moves with it. I am at a loss on what to do now.
I'm currently trying to put animations on VideoView.
If you look at Android source Code, VideoView is basically a SurfaceView, coupled with a MediaPlayer, with a basic management of player's state machine.
Actually, the real 'draw' work seems to be handled by native methods in mediaPlayer (a.k.a. the real player engine implementation on your android device)
We've tested animations on different devices, and found that VideoView's underlying video player behavior/implementation isn't the same among different Android Devices :
some devices handle player's view animations correctly
others DON'T, and just display blur, black screen, or buggy display...
On top of that, VideoView seems to be written directly on memory, so any 'workaround' (like putting an opaque view in front, and setting an animation on that view) doesn't seem to work.
I'd be glad to have others feedback on this :)
Sorry for answering this rather late, but for what it's worth. and if you're looking only to use a 'slide' animation.
Try putting the videoview in a layout and animating the layout.
The way i have it set up in my code is;
AbsoluteLayout Animationlayout = (AbsoluteLayout)findViewById(R.string.frontlayout);
VideoView pvv = new VideoView(getApplicationContext());
pvv.getHolder().addCallback(this);
Animationlayout.addView(pvv);
// load video data in pvv.
Then where you want to animate your videoview to slide;
Animationlayout.animate().xBy(25).setDuration(500).setInterpolator(new BounceInterpolator());
Note that this is the 3.1 animation system.
Not sure of the classic 2.1 way of animating is going to work like this, but it should serve the same.
Stuff like rotating/scaling the layout won't work. Panning the layout around and fading it seem to be the only few things that do work.
Simply use TextureView:
More reference on TextureView here.
I have done ZoomIn - ZoomOut animation on TextureView.
Add animation xml in Res -> anim folder.
zoom_in_out.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<scale
android:duration="1000"
android:fillAfter="false"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.2"
android:toYScale="1.2" />
<set android:interpolator="#android:anim/decelerate_interpolator">
<scale
android:duration="1000"
android:fillBefore="false"
android:fromXScale="1.2"
android:fromYScale="1.2"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="500"
android:toXScale="1.0"
android:toYScale="1.0" />
</set>
</set>
texture_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextureView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/textureView"
android:layout_margin="50dp"
android:background="#android:color/darker_gray"
android:layout_width="wrap_content" android:layout_height="wrap_content">
</TextureView>
TextureViewActivity.java:
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import java.io.IOException;
public class TextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {
private TextureView textureView;
private MediaPlayer mMediaPlayer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.texture_layout);
textureView = (TextureView)findViewById(R.id.textureView);
textureView.setSurfaceTextureListener(this);
textureView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Animation zoomAnimation = AnimationUtils.loadAnimation(TextureViewActivity.this, R.anim.zoom_in_out);
textureView.startAnimation(zoomAnimation);
}
});
}
private String getVideoPath(){
return "android.resource://" + getPackageName() + "/" + R.raw.promovideo;
}
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
Surface surface = new Surface(surfaceTexture);
try {
mMediaPlayer= new MediaPlayer();
mMediaPlayer.setDataSource(TextureViewActivity.this, Uri.parse(getVideoPath()));
mMediaPlayer.setSurface(surface);
mMediaPlayer.prepare();
mMediaPlayer.start();
mMediaPlayer.setLooping(true);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
}
Done