Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

About Permissions #39

Closed
mr-johnlocke8 opened this issue Oct 27, 2020 · 19 comments
Closed

About Permissions #39

mr-johnlocke8 opened this issue Oct 27, 2020 · 19 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@mr-johnlocke8
Copy link

Hello Andrei, thank you for this great plugin!
I have a slight problem with the permissions.
My use case is audio only, so that I don't need camera permissions. Is there any way to prevent the app from asking the user to enable camera permissions?

Also, on Android, is there an option to not ask for local storage permissions?
I need to ask only for microphone permissions.

Thank you.

@AndreiMisiukevich AndreiMisiukevich added the enhancement New feature or request label Oct 27, 2020
@AndreiMisiukevich
Copy link
Owner

Hi, I think this can be enhanced.

Here is the requested permissions list: https://github.com/AndreiMisiukevich/OpenTok-Xamarin.Forms/blob/master/Lib/Xamarin.Forms.OpenTok.Android/Service/PlatformOpenTokService.cs#L23

But you can work around it now for your app if extend this class, override request permission method and put desired ones.
Then register your custom service instead default

// PlatformOpenTokService.Init(); REPLACE THIS LINE BY NEXT LINES

OpenTokPublisherViewRenderer.Preserve();
OpenTokSubscriberViewRenderer.Preserve();
CrossOpenTok.Init(() => new CustomPlatformOpenTokService()); // CustomPlatformOpenTokService your class

@mr-johnlocke8
Copy link
Author

Hi, thank you for the help.
I've implemented a custom service like you said, but when I attempt to open a call page, I get Object reference not set to an instance of an object. This is the relevant stack trace:

OpenTokViewRenderer.OnElementChanged() : OpenTokViewRenderer.cs:31
OpenTokViewRenderer.ResetControl() : OpenTokViewRenderer.cs:40
OpenTokSubscriberViewRenderer.GetNativeView() : OpenTokSubscriberViewRenderer.cs:37 <-- Here the exception

@AndreiMisiukevich
Copy link
Owner

Did you register it CrossOpenTok.Init(() => new CustomPlatformOpenTokService()) ?

@mr-johnlocke8
Copy link
Author

Yes.

Did exactly like this:

// PlatformOpenTokService.Init(); REPLACE THIS LINE BY NEXT LINES

OpenTokPublisherViewRenderer.Preserve();
OpenTokSubscriberViewRenderer.Preserve();
CrossOpenTok.Init(() => new CustomPlatformOpenTokService()); // CustomPlatformOpenTokService your class

@AndreiMisiukevich
Copy link
Owner

Please show me your CustomPlatformOpenTokService class

@mr-johnlocke8
Copy link
Author

Sure. Basically copied PlatformOpenTokService and just changed the permissions.

    [Preserve(AllMembers = true)]
    public sealed class CustomPlatformOpenTokService : BaseOpenTokService
    {
        public event Action PublisherUpdated;
        public event Action SubscriberUpdated;

        private readonly string[] _requestPermissions = {
            Manifest.Permission.RecordAudio,
            Manifest.Permission.Internet,
            Manifest.Permission.AccessNetworkState
        };
        private readonly object _sessionLocker = new object();
        private readonly ObservableCollection<string> _subscriberStreamIds = new ObservableCollection<string>();
        private readonly Collection<SubscriberKit> _subscribers = new Collection<SubscriberKit>();

        public CustomPlatformOpenTokService()
        {
            _subscriberStreamIds.CollectionChanged += OnSubscriberStreamIdsCollectionChanged;
            PropertyChanged += OnPropertyChanged;
            StreamIdCollection = new ReadOnlyObservableCollection<string>(_subscriberStreamIds);
            Subscribers = new ReadOnlyCollection<SubscriberKit>(_subscribers);
        }

        public static CustomPlatformOpenTokService Instance => CrossOpenTok.Current as CustomPlatformOpenTokService;

        public override ReadOnlyObservableCollection<string> StreamIdCollection { get; }
        public ReadOnlyCollection<SubscriberKit> Subscribers { get; }
        public Session Session { get; private set; }
        public PublisherKit PublisherKit { get; private set; }

        public static void Init()
        {
            OpenTokPublisherViewRenderer.Preserve();
            OpenTokSubscriberViewRenderer.Preserve();
            CrossOpenTok.Init(() => new CustomPlatformOpenTokService());
        }

        public override bool TryStartSession()
        {
            lock (_sessionLocker)
            {
                if (!CheckPermissions() ||
                    string.IsNullOrWhiteSpace(ApiKey) ||
                    string.IsNullOrWhiteSpace(SessionId) ||
                    string.IsNullOrWhiteSpace(UserToken))
                {
                    return false;
                }

                EndSession();
                IsSessionStarted = true;

                using (var builder = new Session.Builder(CrossCurrentActivity.Current.AppContext, ApiKey, SessionId)
                    .SessionOptions(new SessionOptions()))
                {
                    Session = builder.Build();
                    Session.ConnectionDestroyed += OnConnectionDestroyed;
                    Session.Connected += OnConnected;
                    Session.StreamReceived += OnStreamReceived;
                    Session.StreamDropped += OnStreamDropped;
                    Session.Error += OnError;
                    Session.Signal += OnSignal;
                    Session.Connect(UserToken);
                }
                return true;
            }
        }

        public override void EndSession()
        {
            lock (_sessionLocker)
            {
                try
                {
                    if (Session == null)
                    {
                        return;
                    }

                    foreach (var subscriberKit in _subscribers)
                    {
                        ClearSubscriber(subscriberKit);
                    }
                    _subscribers.Clear();
                    _subscriberStreamIds.Clear();

                    if (PublisherKit != null)
                    {
                        using (PublisherKit)
                        {
                            PublisherKit.PublishAudio = false;
                            PublisherKit.PublishVideo = false;
                            PublisherKit.StreamCreated -= OnPublisherStreamCreated;
                        }
                        PublisherKit = null;
                    }

                    RaisePublisherUpdated().
                        RaiseSubscriberUpdated();

                    if (Session != null)
                    {
                        using (Session)
                        {
                            Session.ConnectionDestroyed -= OnConnectionDestroyed;
                            Session.Connected -= OnConnected;
                            Session.StreamReceived -= OnStreamReceived;
                            Session.StreamDropped -= OnStreamDropped;
                            Session.Error -= OnError;
                            Session.Signal -= OnSignal;
                            Session.Disconnect();
                        }
                        Session = null;
                    }

                }
                finally
                {
                    IsSessionStarted = false;
                    IsPublishingStarted = false;
                }
            }
        }

        public override bool CheckPermissions()
        {
            var activity = CrossCurrentActivity.Current.Activity;
            var shouldGrantPermissions = _requestPermissions.Any(permission => ContextCompat.CheckSelfPermission(activity, permission) != (int)Permission.Granted);
            if (shouldGrantPermissions)
            {
                ActivityCompat.RequestPermissions(activity, _requestPermissions, 0);
            }
            return !shouldGrantPermissions;
        }

        public override Task<bool> SendMessageAsync(string message)
        {
            Session.SendSignal(string.Empty, message);
            return Task.FromResult(true);
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case nameof(IsVideoPublishingEnabled):
                    UpdatePublisherProperty(p => p.PublishVideo = IsVideoPublishingEnabled);
                    return;
                case nameof(IsAudioPublishingEnabled):
                    UpdatePublisherProperty(p => p.PublishAudio = IsAudioPublishingEnabled);
                    return;
                case nameof(IsVideoSubscriptionEnabled):
                    UpdateSubscriberProperty(s => s.SubscribeToVideo = IsVideoSubscriptionEnabled);
                    return;
                case nameof(IsAudioSubscriptionEnabled):
                    UpdateSubscriberProperty(s => s.SubscribeToAudio = IsAudioSubscriptionEnabled);
                    return;
            }
        }

        private void UpdatePublisherProperty(Action<PublisherKit> updateAction)
        {
            if (PublisherKit == null)
            {
                return;
            }
            updateAction?.Invoke(PublisherKit);
        }

        private void UpdateSubscriberProperty(Action<SubscriberKit> updateAction)
        {
            foreach (var subscriberKit in _subscribers)
            {
                updateAction?.Invoke(subscriberKit);
            }
        }

        public override void CycleCamera() => (PublisherKit as Publisher)?.CycleCamera();

        private void OnConnectionDestroyed(object sender, Session.ConnectionDestroyedEventArgs e)
            => RaiseSubscriberUpdated();
        
        private void OnConnected(object sender, Session.ConnectedEventArgs e)
        {
            if (Session == null || PublisherKit != null)
            {
                return;
            }
            
            using (var builder = new Publisher.Builder(CrossCurrentActivity.Current.AppContext)
                .Resolution(Publisher.CameraCaptureResolution.High)
                .Name("XamarinOpenTok"))
            {
                PublisherKit = builder.Build();
                PublisherKit.PublishVideo = IsVideoPublishingEnabled;
                PublisherKit.PublishAudio = IsAudioPublishingEnabled;
                PublisherKit.SetStyle(BaseVideoRenderer.StyleVideoScale, BaseVideoRenderer.StyleVideoFill);
                PublisherKit.StreamCreated += OnPublisherStreamCreated;

                Session.Publish(PublisherKit);
                RaisePublisherUpdated();
            }
        }

        private void OnStreamReceived(object sender, Session.StreamReceivedEventArgs e)
        {
            if (Session == null)
            {
                return;
            }

            DropStream(e.P1?.StreamId);

            using (var builder = new Subscriber.Builder(CrossCurrentActivity.Current.AppContext, e.P1))
            {
                var subscriberKit = builder.Build();
                subscriberKit.SubscribeToAudio = IsAudioSubscriptionEnabled;
                subscriberKit.SubscribeToVideo = IsVideoSubscriptionEnabled;
                subscriberKit.SetStyle(BaseVideoRenderer.StyleVideoScale, BaseVideoRenderer.StyleVideoFill);

                subscriberKit.Connected += OnSubscriberConnected;
                subscriberKit.StreamDisconnected += OnStreamDisconnected;
                subscriberKit.SubscriberDisconnected += OnSubscriberDisconnected;
                subscriberKit.VideoDataReceived += OnSubscriberVideoDataReceived;
                subscriberKit.VideoDisabled += OnSubscriberVideoDisabled;
                subscriberKit.VideoEnabled += OnSubscriberVideoEnabled;

                Session.Subscribe(subscriberKit);
                var streamId = e.P1.StreamId;
                _subscribers.Add(subscriberKit);
                _subscriberStreamIds.Add(streamId);
            }
        }

        private void OnStreamDropped(object sender, Session.StreamDroppedEventArgs e)
            => DropStream(e.P1?.StreamId);

        private void OnError(object sender, Session.ErrorEventArgs e)
        {
            RaiseError(e.P1?.Message);
            EndSession();
        }

        private void OnSubscriberVideoDisabled(object sender, SubscriberKit.VideoDisabledEventArgs e)
            => RaiseSubscriberUpdated();

        private void OnSubscriberVideoDataReceived(object sender, SubscriberKit.VideoDataReceivedEventArgs e)
            => RaiseSubscriberUpdated();

        private void OnSubscriberVideoEnabled(object sender, SubscriberKit.VideoEnabledEventArgs e)
            => RaiseSubscriberUpdated();

        private void OnSubscriberConnected(object sender, SubscriberKit.ConnectedEventArgs e)
            => RaisePublisherUpdated().RaiseSubscriberUpdated();

        private void OnSubscriberDisconnected(object sender, SubscriberKit.SubscriberListenerDisconnectedEventArgs e)
            => RaisePublisherUpdated().RaiseSubscriberUpdated();

        private void OnStreamDisconnected(object sender, SubscriberKit.StreamListenerDisconnectedEventArgs e)
            => RaisePublisherUpdated().RaiseSubscriberUpdated();

        private void DropStream(string streamId)
        {
            var subscriberKit = _subscribers.FirstOrDefault(x => x.Stream?.StreamId == streamId);
            if (subscriberKit != null)
            {
                ClearSubscriber(subscriberKit);
                _subscribers.Remove(subscriberKit);
            }
            _subscriberStreamIds.Remove(streamId);
            RaiseSubscriberUpdated();
        }

        private CustomPlatformOpenTokService RaiseSubscriberUpdated()
        {
            SubscriberUpdated?.Invoke();
            return this;
        }

        private CustomPlatformOpenTokService RaisePublisherUpdated()
        {
            PublisherUpdated?.Invoke();
            return this;
        }

        private void OnPublisherStreamCreated(object sender, PublisherKit.StreamCreatedEventArgs e)
            => IsPublishingStarted = true;

        private void OnSignal(object sender, Session.SignalEventArgs e)
            => RaiseMessageReceived(e.P2);

        private void ClearSubscriber(SubscriberKit subscriberKit)
        {
            using (subscriberKit)
            {
                subscriberKit.SubscribeToAudio = false;
                subscriberKit.SubscribeToVideo = false;
                subscriberKit.Connected -= OnSubscriberConnected;
                subscriberKit.StreamDisconnected -= OnStreamDisconnected;
                subscriberKit.SubscriberDisconnected -= OnSubscriberDisconnected;
                subscriberKit.VideoDataReceived -= OnSubscriberVideoDataReceived;
                subscriberKit.VideoDisabled -= OnSubscriberVideoDisabled;
                subscriberKit.VideoEnabled -= OnSubscriberVideoEnabled;
            }
        }

        public sealed class SessionOptions : Session.SessionOptions
        {
            public static bool IsCameraTwoCapable { private get; set; } = true;

            public override bool UseTextureViews() => true;

            public override bool IsCamera2Capable => IsCameraTwoCapable;
        }
    }

@AndreiMisiukevich
Copy link
Owner

CustomPlatformOpenTokService

Wow) I meant just

[Preserve(AllMembers = true)]
class CustomPlatformOpenTokService: PlatformOpenTokService {
        private readonly string[] _requestPermissions = {
            Manifest.Permission.RecordAudio,
            Manifest.Permission.Internet,
            Manifest.Permission.AccessNetworkState
        };

        public override bool CheckPermissions()
        {
            var activity = CrossCurrentActivity.Current.Activity;
            var shouldGrantPermissions = _requestPermissions.Any(permission => ContextCompat.CheckSelfPermission(activity, permission) != (int)Permission.Granted);
            if (shouldGrantPermissions)
            {
                ActivityCompat.RequestPermissions(activity, _requestPermissions, 0);
            }
            return !shouldGrantPermissions;
        } 
}

Well if it doesn't help I need to take a look

@mr-johnlocke8
Copy link
Author

You can't inherit from sealed class tho.

@AndreiMisiukevich
Copy link
Owner

You can't inherit from sealed class tho.

Oh, you are right... Need to update it

@mr-johnlocke8
Copy link
Author

I'd be happy to make those adjustments, but I need to make sure it works.
As a temporary hack I've "reimplemented" the entire class and just changed the permissions, but I get this error:

Hi, thank you for the help.
I've implemented a custom service like you said, but when I attempt to open a call page, I get Object reference not set to an instance of an object. This is the relevant stack trace:

OpenTokViewRenderer.OnElementChanged() : OpenTokViewRenderer.cs:31
OpenTokViewRenderer.ResetControl() : OpenTokViewRenderer.cs:40
OpenTokSubscriberViewRenderer.GetNativeView() : OpenTokSubscriberViewRenderer.cs:37 <-- Here the exception

Do you have an idea about this problem? Where can it be fixed?

@AndreiMisiukevich
Copy link
Owner

AndreiMisiukevich commented Oct 28, 2020

Do you have an idea about this problem? Where can it be fixed?

Yes, sure. And I will fix it today

AndreiMisiukevich added a commit that referenced this issue Oct 28, 2020
@AndreiMisiukevich
Copy link
Owner

CrossOpenTok.Current.Permissions = OpenTokPermission.RecordAudio;

@mr-johnlocke8
Copy link
Author

Hi Andrei,
Thank you for the changes.

Android
As soon as I open the call page, I get the following error:
validateClientPermissionsLocked:1120: Caller "com.example.app" (PID 10363, UID 24879) cannot open camera "1" without camera permission

iOS
It just asks me for Camera permissions.

This is the .xaml side

                <tok:OpenTokSubscriberView
                    AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
                    AbsoluteLayout.LayoutFlags="All" />

                <tok:OpenTokPublisherView
                    IsVideoViewRunning="False" />

App.xaml.cs

            CrossOpenTok.Current.IsVideoPublishingEnabled = false;
            CrossOpenTok.Current.Permissions = OpenTokPermission.RecordAudio;

@AndreiMisiukevich
Copy link
Owner

Do you see this bug on iOS only? Or both iOS and Android

@mr-johnlocke8
Copy link
Author

mr-johnlocke8 commented Oct 29, 2020

Only Android raise the said exception. But iOS still ask for camera permissions.
I may add that the subscriber view does show video, only the publisher is voice only.

@AndreiMisiukevich
Copy link
Owner

Only Android raise the said exception. But iOS still ask for camera permissions.
I may add that the subscriber view does show video, only the publisher is voice only.

Would you like to debug the plugin?)

@mr-johnlocke8
Copy link
Author

Sure!
Should I clone the repository into my project and uninstall the nuget package?

@AndreiMisiukevich
Copy link
Owner

Sure!
Should I clone the repository into my project and uninstall the nuget package?

I guess yes

@AndreiMisiukevich AndreiMisiukevich added the help wanted Extra attention is needed label Oct 30, 2020
@AndreiMisiukevich
Copy link
Owner

1.3.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants