InCall.xaml.cs 16.4 KB
Newer Older
1 2
using Linphone.Agents;
using Linphone.Core;
3
using Linphone.Model;
4
using Linphone.Resources;
5
using Microsoft.Xna.Framework.GamerServices;
6
using System;
7
using System.Collections.Generic;
8
using System.ComponentModel;
9
using System.Diagnostics;
10
using System.Threading;
11
using System.Threading.Tasks;
12
using System.Windows;
13
using System.Windows.Controls;
14
using System.Windows.Media.Imaging;
15
using System.Windows.Navigation;
16 17 18

namespace Linphone.Views
{
19 20 21
    /// <summary>
    /// InCall page, displayed for both incoming and outgoing calls.
    /// </summary>
22
    public partial class InCall : BasePage, MuteChangedListener, PauseChangedListener, CallUpdatedByRemoteListener
23 24 25 26 27 28 29
    {
        private const string speakerOn = "/Assets/AppBar/speaker.png";
        private const string speakerOff = "/Assets/AppBar/speaker.png";
        private const string micOn = "/Assets/AppBar/mic.png";
        private const string micOff = "/Assets/AppBar/mic.png";
        private const string pauseOn = "/Assets/AppBar/play.png";
        private const string pauseOff = "/Assets/AppBar/pause.png";
Ghislain MARY's avatar
Ghislain MARY committed
30 31
        private const string videoOn = "/Assets/AppBar/feature.video.png";
        private const string videoOff = "/Assets/AppBar/feature.video.png";
32

33 34
        private Timer oneSecondTimer;
        private Timer fadeTimer;
35
        private DateTimeOffset startTime;
36

37 38 39
        /// <summary>
        /// Public constructor.
        /// </summary>
40
        public InCall()
41
            : base(new InCallModel())
42 43
        {
            InitializeComponent();
44 45 46 47

            var call = LinphoneManager.Instance.LinphoneCore.GetCurrentCall();
            if (call != null && call.GetState() == Core.LinphoneCallState.StreamsRunning)
            {
48
                PauseStateChanged(call, false);
49
            }
50 51
        }

52 53 54 55
        /// <summary>
        /// Method called when the page is displayed.
        /// Searches for a matching contact using the current call address or number and display information if found.
        /// </summary>
56
        protected override async void OnNavigatedTo(NavigationEventArgs nee)
57
        {
58
            // Create LinphoneCore if not created yet, otherwise do nothing
59
            Task t = LinphoneManager.Instance.InitLinphoneCore();
60

61 62 63
            base.OnNavigatedTo(nee);
            this.ViewModel.MuteListener = this;
            this.ViewModel.PauseListener = this;
64
            this.ViewModel.CallUpdatedByRemoteListener = this;
65

66 67
            if (NavigationContext.QueryString.ContainsKey("sip"))
            {
68
                String calledNumber = NavigationContext.QueryString["sip"];
69 70 71 72
                if (calledNumber.StartsWith("sip:"))
                {
                    calledNumber = calledNumber.Substring(4);
                }
73
                // While we dunno if the number matches a contact one, we consider it won't and we display the phone number as username
74
                Contact.Text = calledNumber;
75

76 77 78 79 80 81
                if (calledNumber != null && calledNumber.Length > 0)
                {
                    ContactManager cm = ContactManager.Instance;
                    cm.ContactFound += cm_ContactFound;
                    cm.FindContact(calledNumber);
                }
82
            }
83 84

            await t;
85 86
        }

87 88 89 90 91
        /// <summary>
        /// Method called when the page is leaved.
        /// </summary>
        protected override void OnNavigatedFrom(NavigationEventArgs nee)
        {
92
            if (oneSecondTimer != null)
93
            {
94 95 96 97 98 99
                oneSecondTimer.Dispose();
            }
            if (fadeTimer != null)
            {
                fadeTimer.Dispose();
                fadeTimer = null;
100 101
            }

102 103 104 105 106
            base.OnNavigatedFrom(nee);
            this.ViewModel.MuteListener = null;
            this.ViewModel.PauseListener = null;
        }

107 108 109
        /// <summary>
        /// Callback called when the search on a phone number for a contact has a match
        /// </summary>
110 111
        private void cm_ContactFound(object sender, ContactFoundEventArgs e)
        {
112
            if (e.ContactFound != null)
113
            {
114 115 116 117 118 119 120 121 122
                Contact.Text = e.ContactFound.DisplayName;
                if (e.PhoneLabel != null)
                {
                    Number.Text = e.PhoneLabel + " : " + e.PhoneNumber;
                }
                else
                {
                    Number.Text = e.PhoneNumber;
                }
123 124 125 126 127
            }
        }

        private void hangUp_Click(object sender, RoutedEventArgs e)
        {
128
            if (oneSecondTimer != null)
129
            {
130
                oneSecondTimer.Dispose();
131
            }
132
            LinphoneManager.Instance.EndCurrentCall();
133 134 135 136 137
        }

        private void speaker_Click_1(object sender, RoutedEventArgs e)
        {
            bool isSpeakerToggled = (bool)speaker.IsChecked;
138 139 140 141 142 143 144 145 146 147
            try
            {
                LinphoneManager.Instance.EnableSpeaker(isSpeakerToggled);
                speakerImg.Source = new BitmapImage(new Uri(isSpeakerToggled ? speakerOn : speakerOff, UriKind.RelativeOrAbsolute));
            }
            catch 
            {
                Logger.Warn("Exception while trying to toggle speaker to {0}", isSpeakerToggled.ToString());
                speaker.IsChecked = !isSpeakerToggled;
            }
148 149 150 151 152
        }

        private void microphone_Click_1(object sender, RoutedEventArgs e)
        {
            bool isMicToggled = (bool)microphone.IsChecked;
153
            LinphoneManager.Instance.MuteMic(isMicToggled);
154 155 156 157 158

            if (isMicToggled)
                LinphoneManager.Instance.CallController.NotifyMuted();
            else
                LinphoneManager.Instance.CallController.NotifyUnmuted();
159 160
        }

161 162 163 164 165 166 167
        private void stats_Click_1(object sender, RoutedEventArgs e)
        {
            bool areStatsVisible = (bool)stats.IsChecked;
            emptyPanel.Visibility = areStatsVisible ? Visibility.Collapsed : Visibility.Visible;
            statsPanel.Visibility = areStatsVisible ? Visibility.Visible : Visibility.Collapsed;
        }

168
        private void video_Click_1(object sender, RoutedEventArgs e)
Ghislain MARY's avatar
Ghislain MARY committed
169 170 171 172 173 174
        {
            bool isVideoToggled = (bool)video.IsChecked;
            if (!LinphoneManager.Instance.EnableVideo(isVideoToggled))
            {
                if (isVideoToggled) video.IsChecked = false;
            }
175
            videoImg.Source = new BitmapImage(new Uri(isVideoToggled ? videoOn : videoOff, UriKind.RelativeOrAbsolute));
Ghislain MARY's avatar
Ghislain MARY committed
176 177
        }

Ghislain MARY's avatar
Ghislain MARY committed
178 179
        private void camera_Click_1(object sender, RoutedEventArgs e)
        {
Ghislain MARY's avatar
Ghislain MARY committed
180
            ((InCallModel)ViewModel).ToggleCameras();
Ghislain MARY's avatar
Ghislain MARY committed
181 182
        }

183 184 185 186 187
        /// <summary>
        /// Called when the mute status of the microphone changes.
        /// </summary>
        public void MuteStateChanged(Boolean isMicMuted)
        {
188
            microphone.IsChecked = isMicMuted;
189 190 191
            microImg.Source = new BitmapImage(new Uri(isMicMuted ? micOn : micOff, UriKind.RelativeOrAbsolute));
        }

192 193 194
        private void pause_Click_1(object sender, RoutedEventArgs e)
        {
            bool isPauseToggled = (bool)pause.IsChecked;
195 196 197 198
            if (isPauseToggled)
                LinphoneManager.Instance.PauseCurrentCall();
            else
                LinphoneManager.Instance.ResumeCurrentCall();
199 200
        }

201 202 203
        /// <summary>
        /// Called when the call changes its state to paused or resumed.
        /// </summary>
204
        public void PauseStateChanged(LinphoneCall call, bool isCallPaused)
205
        {
206
            pause.IsChecked = isCallPaused;
207
            pauseImg.Source = new BitmapImage(new Uri(isCallPaused ? pauseOn : pauseOff, UriKind.RelativeOrAbsolute));
208

209
            if (!isCallPaused)
210
            {
211
                oneSecondTimer = new Timer(new TimerCallback(timerTick), null, 0, 1000);
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
                if (call.GetCurrentParamsCopy().IsVideoEnabled() && !((InCallModel)ViewModel).IsVideoActive)
                {
                    // Show video if it was not shown yet
                    ((InCallModel)ViewModel).IsVideoActive = true;
                    video.IsChecked = true;
                    ButtonsFadeInVideoAnimation.Begin();
                    StartFadeTimer();
                }
                else if (!call.GetCurrentParamsCopy().IsVideoEnabled() && ((InCallModel)ViewModel).IsVideoActive)
                {
                    // Stop video if it is no longer active
                    ((InCallModel)ViewModel).IsVideoActive = false;
                    video.IsChecked = false;
                    ButtonsFadeInAudioAnimation.Begin();
                    StopFadeTimer();
227
                }
228
            }
229
            else if (oneSecondTimer != null)
230
            {
231
                oneSecondTimer.Dispose();
232 233 234
            }
        }

235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
        /// <summary>
        /// Called when the call is updated by the remote party.
        /// </summary>
        /// <param name="call">The call that has been updated</param>
        /// <param name="isVideoAdded">A boolean telling whether the remote party added video</param>
        public void CallUpdatedByRemote(LinphoneCall call, bool isVideoAdded)
        {
            if (isVideoAdded)
            {
                Guide.BeginShowMessageBox(AppResources.VideoActivationPopupCaption,
                    AppResources.VideoActivationPopupContent,
                    new List<String> { "Accept", "Dismiss" },
                    0,
                    MessageBoxIcon.Alert,
                    asyncResult =>
                    {
                        int? res = Guide.EndShowMessageBox(asyncResult);
                        LinphoneCallParams parameters = call.GetCurrentParamsCopy();
                        if (res == 0)
                        {
                            parameters.EnableVideo(true);
                        }
                        LinphoneManager.Instance.LinphoneCore.AcceptCallUpdate(call, parameters);
                    },
                    null);
            }
        }

263 264
        private void timerTick(Object state)
        {
265
            try
266
            {
267 268 269
                LinphoneCall call = LinphoneManager.Instance.LinphoneCore.GetCurrentCall();
                if (call == null)
                {
270
                    oneSecondTimer.Dispose();
271 272 273 274 275 276 277 278 279
                    return;
                }
                startTime = (DateTimeOffset)call.GetCallStartTimeFromContext();
                DateTimeOffset now = DateTimeOffset.Now;
                TimeSpan elapsed = now.Subtract(startTime);
                var ss = elapsed.Seconds;
                var mm = elapsed.Minutes;
                Status.Dispatcher.BeginInvoke(delegate()
                {
280
                    LinphoneCallParams param = call.GetCurrentParamsCopy();
281
                    Status.Text = mm.ToString("00") + ":" + ss.ToString("00");
282

283 284
                    MediaEncryption.Text = String.Format(AppResources.StatMediaEncryption + ": {0}", param.GetMediaEncryption().ToString());

285 286 287 288 289 290 291
                    LinphoneCallStats audioStats = null;
                    try
                    {
                        audioStats = call.GetAudioStats();
                    }
                    catch { }

292
                    if (audioStats != null)
293
                    {
294 295
                        AudioDownBw.Text = String.Format(AppResources.StatDownloadBW + ": {0:0.00} kb/s", audioStats.GetDownloadBandwidth());
                        AudioUpBw.Text = String.Format(AppResources.StatUploadBW + ": {0:0.00} kb/s", audioStats.GetUploadBandwidth());
296
                        ICE.Text = String.Format(AppResources.StatICE + ": {0}", audioStats.GetIceState().ToString()); 
297 298
                    }

299 300 301 302 303 304
                    PayloadType audiopt = param.GetUsedAudioCodec();
                    if (audiopt != null) 
                    {
                        AudioPType.Text = AppResources.StatPayload + ": " + audiopt.GetMimeType() + "/" + audiopt.GetClockRate();
                    }

305
                    if (call.GetCurrentParamsCopy().IsVideoEnabled())
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
                    {
                        LinphoneCallStats videoStats = call.GetVideoStats();
                        if (videoStats != null)
                        {
                            VideoDownBw.Text = String.Format(AppResources.StatDownloadBW + ": {0:0.00} kb/s", videoStats.GetDownloadBandwidth());
                            VideoUpBw.Text = String.Format(AppResources.StatUploadBW + ": {0:0.00} kb/s", videoStats.GetUploadBandwidth());
                        }

                        PayloadType videopt = param.GetUsedVideoCodec();
                        if (videopt != null)
                        {
                            VideoPType.Text = AppResources.StatPayload + ": " + videopt.GetMimeType() + "/" + videopt.GetClockRate();
                        }

                        VideoStats.Visibility = Visibility.Visible;
                    }
                    else
323
                    {
324
                        VideoStats.Visibility = Visibility.Collapsed;
325
                    }
326 327
                });
            } catch {
328
                oneSecondTimer.Dispose();
329
            }
330 331
        }

332 333
        private void dialpad_Click_1(object sender, RoutedEventArgs e)
        {
334
            ((InCallModel)ViewModel).ToggleDialpad();
335 336 337 338
        }

        private void Numpad_Click_1(object sender, RoutedEventArgs e)
        {
339 340 341
            Button button = sender as Button;
            String tag = button.Tag as String;
            LinphoneManager.Instance.LinphoneCore.SendDTMF(Convert.ToChar(tag));
342
        }
343

344 345 346 347 348 349 350 351 352 353
        private void remoteVideo_MediaOpened_1(object sender, System.Windows.RoutedEventArgs e)
        {
            Logger.Msg("RemoteVideo Opened: " + ((MediaElement)sender).Source.AbsoluteUri);
        }

        private void remoteVideo_MediaFailed_1(object sender, System.Windows.ExceptionRoutedEventArgs e)
        {
            Logger.Err("RemoteVideo Failed: " + e.ErrorException.Message);
        }

354 355 356 357 358 359 360 361 362 363
        private void localVideo_MediaOpened_1(object sender, System.Windows.RoutedEventArgs e)
        {
            Logger.Msg("LocalVideo Opened: " + ((MediaElement)sender).Source.AbsoluteUri);
        }

        private void localVideo_MediaFailed_1(object sender, System.Windows.ExceptionRoutedEventArgs e)
        {
            Logger.Err("LocalVideo Failed: " + e.ErrorException.Message);
        }

364 365 366 367 368 369 370
        /// <summary>
        /// Do not allow user to leave the incall page while call is active
        /// </summary>
        protected override void OnBackKeyPress(CancelEventArgs e)
        {
            e.Cancel = true;
        }
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397

        private void ButtonsFadeOutAnimation_Completed(object sender, EventArgs e)
        {
            buttons.Visibility = Visibility.Collapsed;
            statsPanel.Visibility = Visibility.Collapsed;
            Status.Visibility = Visibility.Collapsed;
            Contact.Visibility = Visibility.Collapsed;
            Number.Visibility = Visibility.Collapsed;
        }

        private void HideButtons(Object state)
        {
            Status.Dispatcher.BeginInvoke(delegate()
            {
                ButtonsFadeOutAnimation.Begin();
            });
        }

        private void StartFadeTimer()
        {
            if (fadeTimer != null)
            {
                fadeTimer.Dispose();
            }
            fadeTimer = new Timer(new TimerCallback(HideButtons), null, 4000, Timeout.Infinite);
        }

398 399 400 401 402 403 404 405 406
        private void StopFadeTimer()
        {
            if (fadeTimer != null)
            {
                fadeTimer.Dispose();
                fadeTimer = null;
            }
        }

407 408 409 410 411 412 413
        private void LayoutRoot_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            buttons.Visibility = Visibility.Visible;
            statsPanel.Visibility = ((bool)stats.IsChecked) ? Visibility.Visible : Visibility.Collapsed;
            Status.Visibility = Visibility.Visible;
            Contact.Visibility = Visibility.Visible;
            Number.Visibility = Visibility.Visible;
414 415 416 417 418 419 420 421 422
            if (((InCallModel)ViewModel).IsVideoActive)
            {
                ButtonsFadeInVideoAnimation.Begin();
                StartFadeTimer();
            }
            else
            {
                ButtonsFadeInAudioAnimation.Begin();
            }
423
        }
424 425
    }
}