void ObserveStatus(NSObservedChange e)
        {
            Controller.Volume = _avPlayerViewController.Player.Volume;

            switch (_avPlayerViewController.Player.Status)
            {
            case AVPlayerStatus.Failed:
                Controller.OnMediaFailed();
                break;

            case AVPlayerStatus.ReadyToPlay:
                var duration = _avPlayerViewController.Player.CurrentItem.Duration;

                if (duration.IsIndefinite)
                {
                    Controller.Duration = TimeSpan.Zero;
                }
                else
                {
                    Controller.Duration = TimeSpan.FromSeconds(duration.Seconds);
                }

                Controller.VideoHeight = (int)_avPlayerViewController.Player.CurrentItem.Asset.NaturalSize.Height;
                Controller.VideoWidth  = (int)_avPlayerViewController.Player.CurrentItem.Asset.NaturalSize.Width;
                Controller.OnMediaOpened();
                Controller.Position = Position;
                break;
            }
        }
Beispiel #2
0
        public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
        {
            if (context == _sessionRunningObserveContext)
            {
                var observableChange = new NSObservedChange(change);

                var isSessionRunning = (observableChange.NewValue as NSNumber)?.BoolValue;

                if (isSessionRunning == null)
                {
                    return;
                }

                DispatchQueue.MainQueue.DispatchAsync(() =>
                {
                    Console.WriteLine($"capture session: is running - ${isSessionRunning}");
                    if (isSessionRunning.Value)
                    {
                        _delegate?.CaptureSessionDidResume();
                    }
                    else
                    {
                        _delegate?.CaptureSessionDidSuspend();
                    }
                });
            }
            else
            {
                base.ObserveValue(keyPath, ofObject, change, context);
            }
        }
Beispiel #3
0
        public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
        {
            var ch = new NSObservedChange(change);

            if (context == AVCustomEditPlayerViewControllerRateObservationContext.Handle)
            {
                float    newRate    = ((NSNumber)ch.NewValue).FloatValue;
                NSNumber oldRateNum = (NSNumber)ch.OldValue;
                if (oldRateNum != null && newRate != oldRateNum.FloatValue)
                {
                    UpdatePlayPauseButton();
                    UpdateScrubber();
                    UpdateTimeLabel();
                }
            }
            else if (context == AVCustomEditPlayerViewControllerStatusObservationContext.Handle)
            {
                AVPlayerItem playerItem = ofObject as AVPlayerItem;
                if (playerItem.Status == AVPlayerItemStatus.ReadyToPlay)
                {
                    AddTimeObserverToPlayer();
                }
                else if (playerItem.Status == AVPlayerItemStatus.Failed)
                {
                    Console.WriteLine(playerItem.Error.LocalizedDescription);
                }
            }
            else
            {
                base.ObserveValue(keyPath, ofObject, change, context);
            }
        }
Beispiel #4
0
        void UpdateCursorFromControl(NSObservedChange obj)
        {
            var control = Control;

            if (_selectedTextRangeIsUpdating || control == null || Element == null)
            {
                return;
            }

            var currentSelection  = control.SelectedTextRange;
            int selectionLength   = (int)control.GetOffsetFromPosition(currentSelection.Start, currentSelection.End);
            int newCursorPosition = (int)control.GetOffsetFromPosition(control.BeginningOfDocument, currentSelection.Start);

            _selectedTextRangeIsUpdating = true;
            if (newCursorPosition != Element.CursorPosition)
            {
                ElementController?.SetValueFromRenderer(Entry.CursorPositionProperty, newCursorPosition);
            }

            if (selectionLength != Element.SelectionLength)
            {
                ElementController?.SetValueFromRenderer(Entry.SelectionLengthProperty, selectionLength);
            }
            _selectedTextRangeIsUpdating = false;
        }
        AVPlayerStatus FetchStatus(NSDictionary change)
        {
            var ch       = new NSObservedChange(change);
            var newValue = (NSNumber)ch.NewValue;

            return(newValue == null ? AVPlayerStatus.Unknown : (AVPlayerStatus)newValue.Int32Value);
        }
Beispiel #6
0
        private void OnRunningChanged(NSObservedChange change)
        {
            var isSessionRunning = ((NSNumber)change.NewValue).BoolValue;

            DispatchQueue.MainQueue.DispatchAsync(() =>
            {
                //this.CameraButton.Enabled = isSessionRunning && AVCaptureDevice.DevicesWithMediaType(AVMediaType.Video).Length > 1;
                //this.MetadataObjectTypesButton.Enabled = isSessionRunning;
                //this.SessionPresetsButton.Enabled = isSessionRunning;

                //this.ZoomSlider.Enabled = isSessionRunning;
                //this.ZoomSlider.MaxValue = (float)NMath.Min(this.videoDeviceInput.Device.ActiveFormat.VideoMaxZoomFactor, 8);
                //this.ZoomSlider.Value = (float)(this.videoDeviceInput.Device.VideoZoomFactor);

                // After the session stop running, remove the metadata object overlays,
                // if any, so that if the view appears again, the previously displayed
                // metadata object overlays are removed.
                if (!isSessionRunning)
                {
                    this.RemoveMetadataObjectOverlayLayers();
                }

                // When the session starts running, the aspect ration of the video preview may also change if a new session present was applied .
                // To keep the preview view's region of interest within the visible portion of the video preview, the preview view's region of
                // interest will need to be updates.
                if (isSessionRunning)
                {
                    this.PreviewView.SetRegionOfInterestWithProposedRegionOfInterest(this.PreviewView.RegionOfInterest);
                }
            });
        }
 void timeControlStatusChanged(NSObservedChange obj)
 {
     TimeControlStatusLabel.Text            = (Player != null) ? Player.TimeControlStatus.ToString() : "-";
     TimeControlStatusLabel.BackgroundColor = (Player != null)
                         ? LabelBackgroundColor(Player.TimeControlStatus)
                         : new UIColor(1, 0.9999743700027466f, 0.9999912977218628f, 1);
 }
Beispiel #8
0
        void UpdateCursorFromControl(NSObservedChange obj)
        {
            if (_nativeSelectionIsUpdating || Control == null || Element == null)
            {
                return;
            }

            var currentSelection = Control.SelectedTextRange;

            if (currentSelection != null)
            {
                if (!_cursorPositionChangePending)
                {
                    int newCursorPosition = (int)Control.GetOffsetFromPosition(Control.BeginningOfDocument, currentSelection.Start);
                    if (newCursorPosition != Element.CursorPosition)
                    {
                        SetCursorPositionFromRenderer(newCursorPosition);
                    }
                }

                if (!_selectionLengthChangePending)
                {
                    int selectionLength = (int)Control.GetOffsetFromPosition(currentSelection.Start, currentSelection.End);

                    if (selectionLength != Element.SelectionLength)
                    {
                        SetSelectionLengthFromRenderer(selectionLength);
                    }
                }
            }
        }
Beispiel #9
0
        void OnRunningChanged(NSObservedChange change)
        {
            var running = ((NSNumber)change.NewValue).BoolValue;

            if (!running)
            {
                return;
            }

            DispatchQueue.MainQueue.DispatchAsync(() => {
                // If the region of interest view's region of interest has not
                // been initialized yet, let's set an inital region of interest
                // that is 80% of the shortest side by 25% of the longest side
                // and centered in the root view.
                if (RegionOfInterest.IsEmpty)
                {
                    var width  = NMath.Min(Frame.Width, Frame.Height) * 0.8f;
                    var height = NMath.Max(Frame.Width, Frame.Height) * 0.25f;

                    var newRegionOfInterest = Frame.Inset(Frame.GetMidX() - width / 2, Frame.GetMidY() - height / 2);
                    SetRegionOfInterestWithProposedRegionOfInterest(newRegionOfInterest);
                }

                if (running)
                {
                    SetRegionOfInterestWithProposedRegionOfInterest(RegionOfInterest);
                    RegionOfInterestDidChange?.Invoke(this, EventArgs.Empty);
                }
            });
        }
Beispiel #10
0
 private void Restart(NSObservedChange change)
 {
     if (_isRunning)
     {
         Stop();
         Start();
     }
 }
        void ObserveVolume(NSObservedChange e)
        {
            if (Controller == null || avPlayerViewController.Player == null)
            {
                return;
            }

            Controller.Volume = avPlayerViewController.Player.Volume;
        }
        void OnSizeChanged(NSObservedChange change)
        {
            CGSize oldValue = ((NSValue)change.OldValue).CGSizeValue;
            CGSize newValue = ((NSValue)change.NewValue).CGSizeValue;

            var dy = newValue.Height - oldValue.Height;

            AdjustInputToolbarOnTextViewSizeChanged(dy);
        }
Beispiel #13
0
        private void IsPlayableChanged(NSObservedChange obj)
        {
            if (!Asset.UrlAsset.Playable)
            {
                return;
            }

            playerItem = new AVPlayerItem(Asset.UrlAsset);
            player.ReplaceCurrentItemWithPlayerItem(playerItem);
        }
Beispiel #14
0
        void OnSessionRunningChanged(NSObservedChange change)
        {
            bool isSessionRunning = ((NSNumber)change.NewValue).BoolValue;

            DispatchQueue.MainQueue.DispatchAsync(() => {
                // Only enable the ability to change camera if the device has more than one camera.
                CameraButton.Enabled = isSessionRunning && NumberOfVideoCameras() > 1;
                RecordButton.Enabled = isSessionRunning;
                StillButton.Enabled  = isSessionRunning;
            });
        }
		public override void ObserveValue (NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
		{
			if ((ofObject == CaptureOutput) && (keyPath == capturingStillImageKeypath)) {
				var ch = new NSObservedChange (change);
				var value = (NSNumber)ch.NewValue;
				AnimateVisualShutter (value.BoolValue);
				return;
			}

			base.ObserveValue (keyPath, ofObject, change, context);
		}
Beispiel #16
0
 private void onCalendarSizeChanged(NSObservedChange change)
 {
     if (CalendarIsVisible)
     {
         TopCalendarConstraint.Constant = 0;
     }
     else
     {
         TopCalendarConstraint.Constant = calendarHeight;
     }
 }
 private void IsViewLoadedObserver(NSObservedChange nsObservedChange)
 {
     if (!nsObservedChange.NewValue.Equals(NSNull.Null))
     {
         _viewLifecycleEffect?.RaiseLoaded(Element);
     }
     else if (!nsObservedChange.OldValue.Equals(NSNull.Null))
     {
         _viewLifecycleEffect?.RaiseUnloaded(Element);
     }
 }
Beispiel #18
0
        private void ObserveStatus(NSObservedChange e)
        {
            if (e.NewValue != null)
            {
                if (_avPlayerViewController.Player.Status == AVPlayerStatus.ReadyToPlay)
                {
                    Element?.RaiseMediaOpened();
                }

                System.Diagnostics.Debug.WriteLine(e.NewValue.ToString());
            }
        }
        public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
        {
            if ((ofObject == CaptureOutput) && (keyPath == capturingStillImageKeypath))
            {
                var ch    = new NSObservedChange(change);
                var value = (NSNumber)ch.NewValue;
                AnimateVisualShutter(value.BoolValue);
                return;
            }

            base.ObserveValue(keyPath, ofObject, change, context);
        }
        public void ProgressObserver(NSObservedChange nsObservedChange)
        {
            var progress = Convert.ToInt32(Web.EstimatedProgress);

            if (progress != 1 && _loadingIndicator.Value == 0)
            {
                _loadingIndicator.Up();
            }
            else if (progress == 1)
            {
                _loadingIndicator.Down();
            }
        }
Beispiel #21
0
        private void OnCapturingStillImageChangedHandler(NSObservedChange change)
        {
            bool isCapturingStillImage = ((NSNumber)change.NewValue).BoolValue;

            if (isCapturingStillImage)
            {
                PreviewLayer.Opacity = 0;
                UIView.Animate(0.25, () =>
                {
                    PreviewLayer.Opacity = 1;
                });
            }
        }
        //private void AVPlayerItem_BufferUpdated(NSObservedChange e)
        //{
        //    if (playerItem.PlaybackBufferEmpty)
        //    {
        //        Element.OnBufferingStart();
        //    }
        //    else if (playerItem.PlaybackBufferFull)
        //    {
        //        Element.OnBufferingEnd();
        //    }
        //}

        private void AVPlayerItem_BufferUpdated(NSObservedChange e)
        {
            var isBuffering = !playerItem?.PlaybackLikelyToKeepUp;

            if (isBuffering == true && Element.IsBuffering == false)
            {
                Element.OnBufferingStart();
            }
            else if (Element.IsBuffering)
            {
                Element.OnBufferingEnd();
            }
        }
Beispiel #23
0
        void OnCapturingStillImageChanged(NSObservedChange change)
        {
            bool isCapturingStillImage = ((NSNumber)change.NewValue).BoolValue;

            if (isCapturingStillImage)
            {
                DispatchQueue.MainQueue.DispatchAsync(() => {
                    PreviewView.Layer.Opacity = 0;
                    UIView.Animate(0.25, () => {
                        PreviewView.Layer.Opacity = 1;
                    });
                });
            }
        }
Beispiel #24
0
        void OnViewLoadedObserver(NSObservedChange nSObservedChange)
        {
            if (!nSObservedChange?.NewValue?.Equals(NSNull.Null) ?? false)
            {
                VirtualView?.Load();
            }
            else if (!nSObservedChange?.OldValue?.Equals(NSNull.Null) ?? false)
            {
                VirtualView?.Unload();

                _isLoadedObserverDisposable?.Dispose();
                _isLoadedObserverDisposable = null;
            }
        }
Beispiel #25
0
        protected virtual void LoadedTimeRangesChanged(NSObservedChange obj)
        {
            var buffered = TimeSpan.Zero;

            if (audioPlayer?.CurrentItem != null && audioPlayer.CurrentItem.LoadedTimeRanges.Any())
            {
                buffered =
                    TimeSpan.FromSeconds(
                        audioPlayer.CurrentItem.LoadedTimeRanges.Select(
                            tr => tr.CMTimeRangeValue.Start.Seconds + tr.CMTimeRangeValue.Duration.Seconds).Max());

                Buffered = buffered;
            }
        }
        void ObserveRate(NSObservedChange e)
        {
            switch (Control.Player.Rate)
            {
            case 0.0f:
                Controller.CurrentState = MediaElementState.Paused;
                break;

            case 1.0f:
                Controller.CurrentState = MediaElementState.Playing;
                break;
            }

            Controller.Position = Position;
        }
 void OnViewLoadedObserver(NSObservedChange nSObservedChange)
 {
     if (!nSObservedChange?.NewValue?.Equals(NSNull.Null) ?? false)
     {
         Element?.Load();
         Element?.AttachComponents();
     }
     else if (!nSObservedChange?.OldValue?.Equals(NSNull.Null) ?? false)
     {
         Element?.Unload();
         Element?.DetachComponents();
         _isLoadedObserverDisposable.Dispose();
         _isLoadedObserverDisposable = null;
     }
 }
Beispiel #28
0
 public virtual void FrameChanged(NSObservedChange change)
 {
     if (change.Change == NSKeyValueChange.Setting)
     {
         if (oldRect == CGRect.Empty)
         {
             oldRect = ((NSValue)change.OldValue).CGRectValue;
         }
         CGRect newRect = ((NSValue)change.NewValue).CGRectValue;
         if (oldRect != newRect)
         {
             NavigationScrollView.Frame = new CGRect(0, 0, NavigationView.Frame.Width, NavigationScrollView.Frame.Height);
             SetNavigationViewItemsPosition();
         }
     }
 }
Beispiel #29
0
 private void PlayerItemStatusChanged(NSObservedChange obj)
 {
     if (playerItem.Status == AVPlayerItemStatus.ReadyToPlay)
     {
         if (!readyForPlayback)
         {
             readyForPlayback = true;
             Delegate.PlayerReadyToPlay(this, player);
         }
         else if (playerItem.Status == AVPlayerItemStatus.Failed)
         {
             var error = playerItem.Error;
             Debug.WriteLine($"Error: {error?.LocalizedDescription}");
         }
     }
 }
Beispiel #30
0
        void OnSessionRunningChanged(NSObservedChange change)
        {
            bool isSessionRunning            = ((NSNumber)change.NewValue).BoolValue;
            var  isLivePhotoCaptureSupported = photoOutput.IsLivePhotoCaptureSupported;
            var  isLivePhotoCaptureEnabled   = photoOutput.IsLivePhotoCaptureEnabled;

            DispatchQueue.MainQueue.DispatchAsync(() => {
                // Only enable the ability to change camera if the device has more than one camera.
                CameraButton.Enabled        = isSessionRunning && UniqueDevicePositionsCount(videoDeviceDiscoverySession) > 1;
                RecordButton.Enabled        = isSessionRunning && MovieFileOutput != null;
                PhotoButton.Enabled         = isSessionRunning;
                CaptureModeControl.Enabled  = isSessionRunning;
                LivePhotoModeButton.Enabled = isSessionRunning && isLivePhotoCaptureEnabled;
                LivePhotoModeButton.Hidden  = !(isSessionRunning && isLivePhotoCaptureSupported);
            });
        }
        protected virtual void ObserveRate(NSObservedChange e)
        {
            if (Controller is object)
            {
                switch (avPlayerViewController.Player?.Rate)
                {
                case 0.0f:
                    Controller.CurrentState = MediaElementState.Paused;
                    break;

                default:
                    Controller.CurrentState = MediaElementState.Playing;
                    break;
                }

                Controller.Position = Position;
            }
        }
		void LensPositionChanged (NSObservedChange obj)
		{
			var newValue = obj.NewValue;
			if (newValue != null && newValue != NSNull.Null) {
				AVCaptureFocusMode focusMode = VideoDevice.FocusMode;
				float newLensPosition = newValue.AsFloat ();
				DispatchQueue.MainQueue.DispatchAsync (() => {
					if (focusMode != AVCaptureFocusMode.Locked)
						LensPositionSlider.Value = newLensPosition;
					LensPositionValueLabel.Text = newLensPosition.ToString ("F1", CultureInfo.InvariantCulture);
				});
			}
		}
		public override void ObserveValue (NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
		{
			if (context != IntPtr.Zero) {
				base.ObserveValue (keyPath, ofObject, change, context);
				return;
			}

			var ch = new NSObservedChange (change);

			if (keyPath == "pictureInPicturePossible") {
				// Enable the `PictureInPictureButton` only if `PictureInPicturePossible`
				// is true. If this returns false, it might mean that the application
				// was not configured as shown in the AppDelegate.
				PictureInPictureButton.Enabled = ((NSNumber)ch.NewValue).BoolValue;
			}
		}
		void ReasonForWaitingToPlayChanged (NSObservedChange obj)
		{
			// Hide the indicator view if we are not waiting to minimize stalls.
			WaitingIndicatorView.Hidden = (Player?.ReasonForWaitingToPlay != AVPlayer.WaitingToMinimizeStallsReason);
		}
		void ExposureModeChanged (NSObservedChange obj)
		{
			var newValue = obj.NewValue;
			var oldValue = obj.OldValue;

			if (newValue != null && newValue != NSNull.Null) {
				var newMode = (AVCaptureExposureMode)newValue.AsInt ();
				if (oldValue != null && oldValue != NSNull.Null) {
					var oldMode = (AVCaptureExposureMode)oldValue.AsInt ();

					// It’s important to understand the relationship between ExposureDuration and the minimum frame rate as represented by ActiveVideoMaxFrameDuration.
					// In manual mode, if ExposureDuration is set to a value that's greater than ActiveVideoMaxFrameDuration, then ActiveVideoMaxFrameDuration will
					// increase to match it, thus lowering the minimum frame rate. If ExposureMode is then changed to automatic mode, the minimum frame rate will
					// remain lower than its default. If this is not the desired behavior, the min and max frameRates can be reset to their default values for the
					// current ActiveFormat by setting ActiveVideoMaxFrameDuration and ActiveVideoMinFrameDuration to CMTime.Invalid.
					if (oldMode != newMode && oldMode == AVCaptureExposureMode.Custom) {
						NSError error = null;
						if (VideoDevice.LockForConfiguration (out error)) {
							VideoDevice.ActiveVideoMaxFrameDuration = CMTime.Invalid;
							VideoDevice.ActiveVideoMinFrameDuration = CMTime.Invalid;
							VideoDevice.UnlockForConfiguration ();
						} else {
							Console.WriteLine ($"Could not lock device for configuration: {error}");
						}
					}
				}
				DispatchQueue.MainQueue.DispatchAsync (() => {
					ExposureModeControl.SelectedSegment = Array.IndexOf (exposureModes, newMode);
					ExposureDurationSlider.Enabled = (newMode == AVCaptureExposureMode.Custom);
					ISOSlider.Enabled = (newMode == AVCaptureExposureMode.Custom);

					if (oldValue != null && oldValue != NSNull.Null) {
						var oldMode = (AVCaptureExposureMode)oldValue.AsInt ();
						Console.WriteLine ($"exposure mode: {StringFromExposureMode (oldMode)} -> {StringFromExposureMode (newMode)}");
					} else {
						Console.WriteLine ($"exposure mode: {StringFromExposureMode (newMode)}");
					}
				});
			}
		}
		void ObserveCurrentItemDuration (NSObservedChange change)
		{
			CMTime newDuration;
			var newDurationAsValue = change.NewValue as NSValue;
			newDuration = (newDurationAsValue != null) ? newDurationAsValue.CMTimeValue : CMTime.Zero;
			var hasValidDuration = newDuration.IsNumeric && newDuration.Value != 0;
			var newDurationSeconds = hasValidDuration ? newDuration.Seconds : 0;

			TimeSlider.MaxValue = (float)newDurationSeconds;

			var currentTime = Player.CurrentTime.Seconds;
			TimeSlider.Value = (float)(hasValidDuration ? currentTime : 0);

			PlayPauseButton.Enabled = hasValidDuration;
			TimeSlider.Enabled = hasValidDuration;
		}
		public override void ObserveValue (NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
		{
			var ch = new NSObservedChange (change);
			if (context == RateObservationContext.Handle) {
				//TODO: need debug here.
				float newRate = ((NSNumber)ch.NewValue).FloatValue;
				NSNumber oldRateNum = (NSNumber)ch.OldValue;
				if (oldRateNum != null && newRate != oldRateNum.FloatValue) {
					playing = (newRate != 0.0f || playRateToRestore != 0.0f);
					updatePlayPauseButton ();
					updateScrubber ();
					updateTimeLabel ();
				}
			} else if (context == StatusObservationContext.Handle) {
				AVPlayerItem playerItem = ofObject as AVPlayerItem;
				if (playerItem.Status == AVPlayerItemStatus.ReadyToPlay) {
					/* Once the AVPlayerItem becomes ready to play, i.e.
					   [playerItem status] == AVPlayerItemStatusReadyToPlay,
					   its duration can be fetched from the item. */
					addTimeObserverToPlayer ();
				} else if (playerItem.Status == AVPlayerItemStatus.Failed) {
					reportError (playerItem.Error);
				}
			} else {
				base.ObserveValue (keyPath, ofObject, change, context);
			}
		}
		void ObserveCurrentItemStatus (NSObservedChange change)
		{
			// Display an error if status becomes Failed
			var newStatusAsNumber = change.NewValue as NSNumber;
			AVPlayerItemStatus newStatus = (newStatusAsNumber != null)
				? (AVPlayerItemStatus)newStatusAsNumber.Int32Value
				: AVPlayerItemStatus.Unknown;

			if (newStatus == AVPlayerItemStatus.Failed) {
				HandleError(Player.CurrentItem.Error);
			} else if (newStatus == AVPlayerItemStatus.ReadyToPlay) {
				var asset = Player.CurrentItem != null ? Player.CurrentItem.Asset : null;
				if (asset != null) {

					// First test whether the values of `assetKeysRequiredToPlay` we need
					// have been successfully loaded.
					foreach (var key in assetKeysRequiredToPlay) {
						NSError error;
						if (asset.StatusOfValue(key, out error) == AVKeyValueStatus.Failed) {
							HandleError(error);
							return;
						}
					}

					if (!asset.Playable || asset.ProtectedContent) {
						// We can't play this asset.
						HandleError(null);
						return;
					}

					// The player item is ready to play,
					// setup picture in picture.
					SetupPictureInPicturePlayback ();
				}
			}
		}
		void SessionRunningChanged (NSObservedChange obj)
		{
			var isRunning = false;
			if (obj.NewValue != null && obj.NewValue != NSNull.Null)
				isRunning = obj.NewValue.AsBool ();

			DispatchQueue.MainQueue.DispatchAsync (() => {
				CameraButton.Enabled = isRunning && (AVCaptureDevice.DevicesWithMediaType (AVMediaType.Video).Length > 1);
				RecordButton.Enabled = isRunning && (CaptureModeControl.SelectedSegment == (int)CaptureMode.Movie);
				PhotoButton.Enabled = isRunning;
				HUDButton.Enabled = isRunning;
				CaptureModeControl.Enabled = isRunning;
			});
		}
		void ExposureTargetOffsetChanged (NSObservedChange obj)
		{
			var newValue = obj.NewValue;
			if (newValue != null && newValue != NSNull.Null) {
				float newExposureTargetOffset = newValue.AsFloat ();
				DispatchQueue.MainQueue.DispatchAsync (() => {
					ExposureTargetOffsetSlider.Value = newExposureTargetOffset;
					ExposureTargetOffsetValueLabel.Text = newExposureTargetOffset.ToString ("F1", CultureInfo.InvariantCulture);
				});
			}
		}
		void OnCapturingStillImageChanged (NSObservedChange change)
		{
			bool isCapturingStillImage = ((NSNumber)change.NewValue).BoolValue;
			if (isCapturingStillImage) {
				DispatchQueue.MainQueue.DispatchAsync (() => {
					PreviewView.Layer.Opacity = 0;
					UIView.Animate (0.25, () => {
						PreviewView.Layer.Opacity = 1;
					});
				});
			}
		}
		void OnSessionRunningChanged (NSObservedChange change)
		{
			bool isSessionRunning = ((NSNumber)change.NewValue).BoolValue;
			var isLivePhotoCaptureSupported = photoOutput.IsLivePhotoCaptureSupported;
			var isLivePhotoCaptureEnabled = photoOutput.IsLivePhotoCaptureEnabled;

			DispatchQueue.MainQueue.DispatchAsync (() => {
				// Only enable the ability to change camera if the device has more than one camera.
				CameraButton.Enabled = isSessionRunning && UniqueDevicePositionsCount (videoDeviceDiscoverySession) > 1;
				RecordButton.Enabled = isSessionRunning && MovieFileOutput != null;
				PhotoButton.Enabled = isSessionRunning;
				CaptureModeControl.Enabled = isSessionRunning;
				LivePhotoModeButton.Enabled = isSessionRunning && isLivePhotoCaptureEnabled;
				LivePhotoModeButton.Hidden = !(isSessionRunning && isLivePhotoCaptureSupported);
			});
		}
		void OnSessionRunningChanged (NSObservedChange change)
		{
			bool isSessionRunning = ((NSNumber)change.NewValue).BoolValue;
			DispatchQueue.MainQueue.DispatchAsync (() => {
				// Only enable the ability to change camera if the device has more than one camera.
				CameraButton.Enabled = isSessionRunning && NumberOfVideoCameras () > 1;
				RecordButton.Enabled = isSessionRunning;
				StillButton.Enabled = isSessionRunning;
			});
		}
		public override void ObserveValue (NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
		{
			var ch = new NSObservedChange (change);
			if (context == AVCustomEditPlayerViewControllerRateObservationContext.Handle) {
				float newRate = ((NSNumber)ch.NewValue).FloatValue;
				NSNumber oldRateNum = (NSNumber)ch.OldValue;
				if (oldRateNum != null && newRate != oldRateNum.FloatValue) {
					UpdatePlayPauseButton ();
					UpdateScrubber ();
					UpdateTimeLabel ();
				}
			} else if (context == AVCustomEditPlayerViewControllerStatusObservationContext.Handle) {
				AVPlayerItem playerItem = ofObject as AVPlayerItem;
				if (playerItem.Status == AVPlayerItemStatus.ReadyToPlay) {
					AddTimeObserverToPlayer ();
				} else if (playerItem.Status == AVPlayerItemStatus.Failed) {
					Console.WriteLine (playerItem.Error.LocalizedDescription);
				}
			} else {
				base.ObserveValue (keyPath, ofObject, change, context);
			}
		}
		void WhiteBalanceModeChange (NSObservedChange obj)
		{
			var newValue = obj.NewValue;
			var oldValue = obj.OldValue;

			if (newValue != null && newValue != NSNull.Null) {
				var newMode = (AVCaptureWhiteBalanceMode)newValue.AsInt ();
				DispatchQueue.MainQueue.DispatchAsync (() => {
					WhiteBalanceModeControl.SelectedSegment = Array.IndexOf (whiteBalanceModes, newMode);
					TemperatureSlider.Enabled = (newMode == AVCaptureWhiteBalanceMode.Locked);
					TintSlider.Enabled = (newMode == AVCaptureWhiteBalanceMode.Locked);

					if (oldValue != null && oldValue != NSNull.Null) {
						var oldMode = (AVCaptureWhiteBalanceMode)oldValue.AsInt ();
						Console.WriteLine ($"white balance mode: {StringFromWhiteBalanceMode (oldMode)} -> {StringFromWhiteBalanceMode (newMode)}");
					}
				});
			}
		}
		unsafe void DeviceWhiteBalanceGainsChange (NSObservedChange obj)
		{
			var gains = (NSValue)obj.NewValue;
			if (gains != null) {
				AVCaptureWhiteBalanceGains newGains;
				gains.StoreValueAtAddress ((IntPtr)(void*)&newGains);

				AVCaptureWhiteBalanceTemperatureAndTintValues newTemperatureAndTint = VideoDevice.GetTemperatureAndTintValues (newGains);
				AVCaptureWhiteBalanceMode whiteBalanceMode = VideoDevice.WhiteBalanceMode;
				DispatchQueue.MainQueue.DispatchAsync (() => {
					if (whiteBalanceMode != AVCaptureWhiteBalanceMode.Locked) {
						TemperatureSlider.Value = newTemperatureAndTint.Temperature;
						TintSlider.Value = newTemperatureAndTint.Tint;
					}

					var ci = CultureInfo.InvariantCulture;
					TemperatureValueLabel.Text = ((int)newTemperatureAndTint.Temperature).ToString (ci);
					TintValueLabel.Text = ((int)newTemperatureAndTint.Tint).ToString (ci);
				});
			}
		}
		void OnRunningChanged (NSObservedChange change)
		{
			var running = ((NSNumber)change.NewValue).BoolValue;
			if (!running)
				return;

			DispatchQueue.MainQueue.DispatchAsync (() => {
				// If the region of interest view's region of interest has not
				// been initialized yet, let's set an inital region of interest
				// that is 80% of the shortest side by 25% of the longest side
				// and centered in the root view.
				if (RegionOfInterest.IsEmpty) {
					var width = NMath.Min (Frame.Width, Frame.Height) * 0.8f;
					var height = NMath.Max (Frame.Width, Frame.Height) * 0.25f;

					var newRegionOfInterest = Frame.Inset (Frame.GetMidX () - width / 2, Frame.GetMidY () - height / 2);
					SetRegionOfInterestWithProposedRegionOfInterest (newRegionOfInterest);
				}

				if (running) {
					SetRegionOfInterestWithProposedRegionOfInterest (RegionOfInterest);
					RegionOfInterestDidChange?.Invoke (this, EventArgs.Empty);
				}
			});
		}
		void ISOChanged (NSObservedChange obj)
		{
			var newValue = obj.NewValue;
			if (newValue != null && newValue != NSNull.Null) {
				float newISO = newValue.AsFloat ();
				AVCaptureExposureMode exposureMode = VideoDevice.ExposureMode;

				DispatchQueue.MainQueue.DispatchAsync (() => {
					if (exposureMode != AVCaptureExposureMode.Custom)
						ISOSlider.Value = newISO;
					ISOValueLabel.Text = ((int)newISO).ToString (CultureInfo.InvariantCulture);
				});
			}
		}
		void FocusModeChanged (NSObservedChange obj)
		{
			var newValue = obj.NewValue;
			var oldValue = obj.OldValue;

			if (newValue != null && newValue != NSNull.Null) {
				var newMode = (AVCaptureFocusMode)newValue.AsInt ();
				DispatchQueue.MainQueue.DispatchAsync (() => {
					FocusModeControl.SelectedSegment = Array.IndexOf (focusModes, newMode);
					LensPositionSlider.Enabled = (newMode == AVCaptureFocusMode.Locked);

					if (oldValue != null && oldValue != NSNull.Null) {
						var oldMode = (AVCaptureFocusMode)oldValue.AsInt ();
						Console.WriteLine ($"focus mode: {StringFromFocusMode (oldMode)} -> {StringFromFocusMode (newMode)}");
					} else {
						Console.WriteLine ($"focus mode: {StringFromFocusMode (newMode)}");
					}
				});
			}
		}
		void OnSizeChanged(NSObservedChange change)
		{
			CGSize oldValue = ((NSValue)change.OldValue).CGSizeValue;
			CGSize newValue = ((NSValue)change.NewValue).CGSizeValue;

			var dy = newValue.Height - oldValue.Height;
			AdjustInputToolbarOnTextViewSizeChanged (dy);
		}
		void ObserveCurrentRate (NSObservedChange change)
		{
			// Update playPauseButton type.
			var newRate = ((NSNumber)change.NewValue).DoubleValue;

			UIBarButtonSystemItem style = (Math.Abs(newRate) < float.Epsilon) ? UIBarButtonSystemItem.Play : UIBarButtonSystemItem.Pause;
			var newPlayPauseButton = new UIBarButtonItem(style, PlayPauseButtonWasPressed);

			// Replace the current button with the updated button in the toolbar.
			UIBarButtonItem[] items = Toolbar.Items;

			var playPauseItemIndex = Array.IndexOf(items, PlayPauseButton);
			if (playPauseItemIndex >= 0) {
				items[playPauseItemIndex] = newPlayPauseButton;
				PlayPauseButton = newPlayPauseButton;
				Toolbar.SetItems(items, false);
			}
		}
		void ExposureDurationChanged (NSObservedChange obj)
		{
			var newValue = obj.NewValue;
			var oldValue = obj.OldValue;

			if (newValue != null && newValue != NSNull.Null) {
				double newDurationSeconds = newValue.AsCMTime ().Seconds;
				AVCaptureExposureMode exposureMode = VideoDevice.ExposureMode;

				double minDurationSeconds = Math.Max (VideoDevice.ActiveFormat.MinExposureDuration.Seconds, ExposureMinDuration);
				double maxDurationSeconds = VideoDevice.ActiveFormat.MaxExposureDuration.Seconds;
				// Map from duration to non-linear UI range 0-1
				double p = (newDurationSeconds - minDurationSeconds) / (maxDurationSeconds - minDurationSeconds); // Scale to 0-1
				DispatchQueue.MainQueue.DispatchAsync (() => {
					if (exposureMode != AVCaptureExposureMode.Custom)
						ExposureDurationSlider.Value = (float)Math.Pow(p ,1 / ExposureDurationPower); // Apply inverse power
					ExposureDurationValueLabel.Text = FormatDuration (newDurationSeconds);
				});
			}
		}
		public override void ObserveValue (NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
		{
			if (context != IntPtr.Zero) {
				base.ObserveValue (keyPath, ofObject, change, context);
				return;
			}

			var ch = new NSObservedChange (change);
			if (keyPath == "currentItem.duration") {
				// Update `TimeSlider` and enable/disable controls when `duration` > 0.0

				CMTime newDuration; 
				var newDurationAsValue = ch.NewValue as NSValue;
				newDuration = (newDurationAsValue != null) ? newDurationAsValue.CMTimeValue : CMTime.Zero;
				var hasValidDuration = newDuration.IsNumeric && newDuration.Value != 0;
				var newDurationSeconds = hasValidDuration ? newDuration.Seconds : 0;

				TimeSlider.MaxValue = (float)newDurationSeconds;

				var currentTime = Player.CurrentTime.Seconds;
				TimeSlider.Value = (float)(hasValidDuration ? currentTime : 0);

				PlayPauseButton.Enabled = hasValidDuration;
				TimeSlider.Enabled = hasValidDuration;
			} else if (keyPath == "rate") {
				// Update playPauseButton type.
				var newRate = ((NSNumber)ch.NewValue).DoubleValue;

				UIBarButtonSystemItem style = (newRate == 0) ? UIBarButtonSystemItem.Play : UIBarButtonSystemItem.Pause;
				var newPlayPauseButton = new UIBarButtonItem (style, PlayPauseButtonWasPressed);

				// Replace the current button with the updated button in the toolbar.
				UIBarButtonItem[] items = Toolbar.Items;

				var playPauseItemIndex = Array.IndexOf (items, PlayPauseButton);
				if (playPauseItemIndex >= 0) {
					items [playPauseItemIndex] = newPlayPauseButton;
					PlayPauseButton = newPlayPauseButton;
					Toolbar.SetItems (items, false);
				}
			} else if (keyPath == "currentItem.status") {
				// Display an error if status becomes Failed

				var newStatusAsNumber = ch.NewValue as NSNumber;
				AVPlayerItemStatus newStatus = (newStatusAsNumber != null)
					? (AVPlayerItemStatus)newStatusAsNumber.Int32Value
					: AVPlayerItemStatus.Unknown;

				if (newStatus == AVPlayerItemStatus.Failed) {
					HandleError (Player.CurrentItem.Error);
				} else if (newStatus == AVPlayerItemStatus.ReadyToPlay) {

					var asset = Player.CurrentItem != null ? Player.CurrentItem.Asset : null;
					if (asset != null) {

						// First test whether the values of `assetKeysRequiredToPlay` we need
						// have been successfully loaded.
						foreach (var key in assetKeysRequiredToPlay) {
							NSError error;
							if (asset.StatusOfValue (key, out error) == AVKeyValueStatus.Failed) {
								HandleError (error);
								return;
							}
						}

						if (!asset.Playable || asset.ProtectedContent) {
							// We can't play this asset.
							HandleError (null);
							return;
						}

						// The player item is ready to play,
						// setup picture in picture.
						SetupPictureInPicturePlayback ();
					}
				}
			} else if (keyPath == "pictureInPicturePossible") {
				// Enable the `PictureInPictureButton` only if `PictureInPicturePossible`
				// is true. If this returns false, it might mean that the application
				// was not configured as shown in the AppDelegate.
				PictureInPictureButton.Enabled = ((NSNumber)ch.NewValue).BoolValue;
			}
		}
		void OnRecordingChanged (NSObservedChange change)
		{
			bool isRecording = ((NSNumber)change.NewValue).BoolValue;
			DispatchQueue.MainQueue.DispatchAsync (() => {
				if (isRecording) {
					CameraButton.Enabled = false;
					RecordButton.Enabled = true;
					RecordButton.SetTitle ("Stop", UIControlState.Normal);
				} else {
					// Only enable the ability to change camera if the device has more than one camera.
					CameraButton.Enabled = NumberOfVideoCameras () > 1;
					RecordButton.Enabled = true;
					RecordButton.SetTitle ("Record", UIControlState.Normal);
				}
			});
		}
		AVPlayerStatus FetchStatus (NSDictionary change)
		{
			var ch = new NSObservedChange (change);
			var newValue = (NSNumber)ch.NewValue;
			return newValue == null ? AVPlayerStatus.Unknown : (AVPlayerStatus)newValue.Int32Value;
		}
		void RunningChanged (NSObservedChange obj)
		{
			var isSessionRunning = ((NSNumber)obj.NewValue).BoolValue;

			DispatchQueue.MainQueue.DispatchAsync (() => {
				MetadataObjectTypesButton.Enabled = isSessionRunning;
				SessionPresetsButton.Enabled = isSessionRunning;
				CameraButton.Enabled = isSessionRunning && AVCaptureDevice.DevicesWithMediaType (AVMediaType.Video).Length > 1;
				ZoomSlider.Enabled = isSessionRunning;
				ZoomSlider.MaxValue = (float)NMath.Min (videoDeviceInput.Device.ActiveFormat.VideoMaxZoomFactor, 8);
				ZoomSlider.Value = (float)(videoDeviceInput.Device.VideoZoomFactor);

				// After the session stop running, remove the metadata object overlays,
				// if any, so that if the view appears again, the previously displayed
				// metadata object overlays are removed.
				if (!isSessionRunning)
					RemoveMetadataObjectOverlayLayers ();
			});
		}