Skip to content
Snippets Groups Projects
Select Git revision
  • master
  • release/201811
  • release/201812
  • release/201901
  • release/201902
  • release/201903
  • release/201904
  • release/201905
  • release/201906
  • release/201908
  • release/201912
  • release/202001
  • release/202005
  • release/windows-test/201910
  • release/201808
  • wip/smartlist_refacto
  • wip/patches_poly_2017/JimmyHamel/MathieuGirouxHuppe
17 results

PreviewPage.xaml.cpp

Blame
    • Andreas Traczyk's avatar
      acc4c413
      settings: add the ability to modify video device settings · acc4c413
      Andreas Traczyk authored and Andreas Traczyk's avatar Andreas Traczyk committed
      - adds the ability to select device, resolution, and frame rate
      
      - modifies the initialization of the daemon by seperating the
        registration of the callbacks from the init function and places
        the start and run loop in an IAsyncAction worker thread with
        forced high priority
      
      - uses std::ofstream for debug log file instead of Platform functions
      
      Change-Id: I32439088fe58513c46d11297db4898ca237174e7
      Tuleap: #790
      acc4c413
      History
      settings: add the ability to modify video device settings
      Andreas Traczyk authored and Andreas Traczyk's avatar Andreas Traczyk committed
      - adds the ability to select device, resolution, and frame rate
      
      - modifies the initialization of the daemon by seperating the
        registration of the callbacks from the init function and places
        the start and run loop in an IAsyncAction worker thread with
        forced high priority
      
      - uses std::ofstream for debug log file instead of Platform functions
      
      Change-Id: I32439088fe58513c46d11297db4898ca237174e7
      Tuleap: #790
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    VideoPage.xaml.cpp 38.60 KiB
    /**************************************************************************
    * Copyright (C) 2016 by Savoir-faire Linux                                *
    * Author: Jger Nicolas <nicolas.jager@savoirfairelinux.com>              *
    * Author: Traczyk Andreas <andreas.traczyk@savoirfairelinux.com>          *
    *                                                                         *
    * This program is free software; you can redistribute it and/or modify    *
    * it under the terms of the GNU General Public License as published by    *
    * the Free Software Foundation; either version 3 of the License, or       *
    * (at your option) any later version.                                     *
    *                                                                         *
    * This program is distributed in the hope that it will be useful,         *
    * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
    * GNU General Public License for more details.                            *
    *                                                                         *
    * You should have received a copy of the GNU General Public License       *
    * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
    **************************************************************************/
    #include "pch.h"
    
    #include "VideoPage.xaml.h"
    
    #include <MemoryBuffer.h>   // IMemoryBufferByteAccess
    
    using namespace RingClientUWP::Views;
    using namespace ViewModel;
    using namespace Video;
    
    using namespace Concurrency;
    using namespace Platform;
    using namespace Windows::Devices::Enumeration;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;
    using namespace Windows::Media::Capture;
    using namespace Windows::ApplicationModel::Core;
    using namespace Windows::UI::Core;
    using namespace Windows::UI;
    
    using namespace Windows::Graphics::Display;
    using namespace Windows::Graphics::Imaging;
    using namespace Windows::Media;
    using namespace Windows::UI::Xaml::Media::Imaging;
    using namespace Windows::UI::Xaml::Media::Animation;
    using namespace Windows::UI::Xaml::Shapes;
    using namespace Windows::Devices::Sensors;
    
    using namespace Windows::UI::Input;
    
    VideoPage::VideoPage()
    {
        InitializeComponent();
    
        barFading = true;
    
        Page::NavigationCacheMode = Navigation::NavigationCacheMode::Required;
    
        VideoManager::instance->rendererManager()->writeVideoFrame +=
            ref new WriteVideoFrame([this](String^ id, uint8_t* buf, int width, int height)
        {
            CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(CoreDispatcherPriority::High,
            ref new DispatchedHandler([=]() {
                try {
                    currentRendererWrapper = VideoManager::instance->rendererManager()->renderer(id);
                    if (!currentRendererWrapper) {
                        return;
                    }
                    else {
                        currentRendererWrapper->isRendering = true;
                        create_task(WriteFrameAsSoftwareBitmapAsync(id, buf, width, height))
                        .then([=](task<void> previousTask) {
                            try {
                                previousTask.get();
                            }
                            catch (Platform::Exception^ e) {
                                EXC_(e);
                            }
                        });
                    }
                }
                catch(Platform::COMException^ e) {
                    EXC_(e);
                }
            }));
        });
    
        VideoManager::instance->captureManager()->startPreviewing +=
            ref new StartPreviewing([this]()
        {
            _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Visible;
            _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Visible;
            _PreviewImage_->FlowDirection = VideoManager::instance->captureManager()->mirroringPreview ?
                                          Windows::UI::Xaml::FlowDirection::RightToLeft :
                                          Windows::UI::Xaml::FlowDirection::LeftToRight;
    
            double aspectRatio = VideoManager::instance->captureManager()->aspectRatio();
    
            _PreviewImage_->Height = ( _videoContent_->ActualHeight / 4 );
            _PreviewImage_->Width = _PreviewImage_->Height * aspectRatio;
            _PreviewImageRect_->Width = _PreviewImage_->Width;
            _PreviewImageRect_->Height = _PreviewImage_->Height;
        });
    
        VideoManager::instance->captureManager()->stopPreviewing +=
            ref new StopPreviewing([this]()
        {
            _PreviewImage_->Source = nullptr;
            _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
            _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        });
    
        VideoManager::instance->captureManager()->getSink +=
            ref new GetSink([this]()
        {
            return _PreviewImage_;
        });
    
        VideoManager::instance->rendererManager()->clearRenderTarget +=
            ref new ClearRenderTarget([this]()
        {
            _IncomingVideoImage_->Source = nullptr;
        });
    
        RingD::instance->windowResized +=
            ref new WindowResized([=](float width, float height)
        {
        });
    
        RingD::instance->incomingAccountMessage +=
            ref new IncomingAccountMessage([&](String^ accountId, String^ from, String^ payload)
        {
            scrollDown();
        });
    
        RingD::instance->vCardUpdated += ref new VCardUpdated([&](Contact^ contact)
        {
            Utils::runOnUIThread([this, contact]() {
                SmartPanelItemsViewModel::instance->update({ "_bestName2", "_avatarImage" });
                updatePageContent();
            });
        });
    
        RingD::instance->stateChange +=
            ref new StateChange([&](String^ callId, CallStatus state, int code)
        {
            switch (state) {
            case CallStatus::IN_PROGRESS:
            {
                for (auto it : SmartPanelItemsViewModel::instance->itemsList)
                    if (it->_callStatus != CallStatus::IN_PROGRESS && it->_callId != callId)
                        RingD::instance->pauseCall(Utils::toString(it->_callId));
    
                _callPaused_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
                _IncomingVideoImage_->Visibility = Windows::UI::Xaml::Visibility::Visible;
    
                callTimerUpdater->Start();
                updateCallTimer(nullptr, nullptr);
    
                RingD::instance->startSmartInfo(500);
                videoPage_InputHandlerToken = Window::Current->CoreWindow->KeyDown +=
                    ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &VideoPage::_corewindow__KeyDown);
    
                break;
            }
            case CallStatus::ENDED:
            {
                Video::VideoManager::instance->rendererManager()->raiseClearRenderTarget();
    
                if (RingD::instance->isFullScreen)
                    RingD::instance->setWindowedMode();
    
                /* "close" the chat panel */
                closeChatPanel();
    
                callTimerUpdater->Stop();
    
                //RingD::instance->stopSmartInfo();
                Window::Current->CoreWindow->KeyDown -= videoPage_InputHandlerToken;
                _smartInfoBorder_->Visibility = VIS::Collapsed;
    
                break;
            }
            case CallStatus::PEER_PAUSED:
            case CallStatus::PAUSED:
                _callPaused_->Visibility = Windows::UI::Xaml::Visibility::Visible;
                _IncomingVideoImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
                break;
            }
        });
    
        RingD::instance->messageDataLoaded += ref new MessageDataLoaded([&]() { scrollDown(); });
    
        RingD::instance->updateSmartInfo += ref new RingClientUWP::UpdateSmartInfo(this, &RingClientUWP::Views::VideoPage::OnsmartInfoUpdated);
    
        RingD::instance->incomingMessage += ref new RingClientUWP::IncomingMessage(this, &RingClientUWP::Views::VideoPage::OnincomingMessage);
        RingD::instance->incomingVideoMuted += ref new RingClientUWP::IncomingVideoMuted(this, &RingClientUWP::Views::VideoPage::OnincomingVideoMuted);
        VideoManager::instance->captureManager()->startPreviewing += ref new RingClientUWP::StartPreviewing(this, &RingClientUWP::Views::VideoPage::OnstartPreviewing);
        VideoManager::instance->captureManager()->stopPreviewing += ref new RingClientUWP::StopPreviewing(this, &RingClientUWP::Views::VideoPage::OnstopPreviewing);
        RingD::instance->audioMuted += ref new RingClientUWP::AudioMuted(this, &RingClientUWP::Views::VideoPage::OnaudioMuted);
        RingD::instance->videoMuted += ref new RingClientUWP::VideoMuted(this, &RingClientUWP::Views::VideoPage::OnvideoMuted);
    
        InitManipulationTransforms();
    
        _PreviewImage_->ManipulationDelta += ref new ManipulationDeltaEventHandler(this, &VideoPage::PreviewImage_ManipulationDelta);
        _PreviewImage_->ManipulationCompleted += ref new ManipulationCompletedEventHandler(this, &VideoPage::PreviewImage_ManipulationCompleted);
        _PreviewImage_->ManipulationMode =
            ManipulationModes::TranslateX |
            ManipulationModes::TranslateY;
    
        _PreviewImageResizer_->ManipulationDelta += ref new ManipulationDeltaEventHandler(this, &VideoPage::PreviewImageResizer_ManipulationDelta);
        _PreviewImageResizer_->ManipulationCompleted += ref new ManipulationCompletedEventHandler(this, &VideoPage::PreviewImageResizer_ManipulationCompleted);
        _PreviewImageResizer_->ManipulationMode = ManipulationModes::TranslateY;
    
        _chatPanelResizeBarGrid_->ManipulationDelta += ref new ManipulationDeltaEventHandler(this, &VideoPage::_chatPanelResizeBarGrid__ManipulationDelta);
        _chatPanelResizeBarGrid_->ManipulationCompleted += ref new ManipulationCompletedEventHandler(this, &VideoPage::_chatPanelResizeBarGrid__ManipulationCompleted);
        _chatPanelResizeBarGrid_->ManipulationMode =
            ManipulationModes::TranslateX |
            ManipulationModes::TranslateY;
    
        TimeSpan timeSpan;
        timeSpan.Duration = static_cast<long long>(1e7);
        callTimerUpdater = ref new DispatcherTimer;
        callTimerUpdater->Interval = timeSpan;
        callTimerUpdater->Tick += ref new Windows::Foundation::EventHandler<Object^>(this, &VideoPage::updateCallTimer);
    
        showSmartInfo = false;
    }
    
    void
    VideoPage::OnsmartInfoUpdated(const std::map<std::string, std::string>& info)
    {
        auto smartInfo = Utils::convertMap(info);
    
        if (auto selectedItem = SmartPanelItemsViewModel::instance->_selectedItem)
            _si_CallId_->Text = "CallID: " + selectedItem->_callId;
    
        if (smartInfo->HasKey("local FPS"))
            _si_fps1_->Text = smartInfo->Lookup("local FPS");
        if (smartInfo->HasKey("local video codec"))
            _si_vc1_->Text = smartInfo->Lookup("local video codec");
        if (smartInfo->HasKey("local audio codec"))
            _si_ac1_->Text = smartInfo->Lookup("local audio codec");
    
        auto localResolution = VideoManager::instance->captureManager()->activeDevice->currentResolution();
        _si_res1_->Text = localResolution->getFriendlyName();
    
        if (smartInfo->HasKey("remote FPS"))
            _si_fps2_->Text = smartInfo->Lookup("remote FPS") ;
        if (smartInfo->HasKey("remote video codec"))
            _si_vc2_->Text = smartInfo->Lookup("remote video codec");
        if (smartInfo->HasKey("remote audio codec"))
            _si_ac2_->Text = smartInfo->Lookup("remote audio codec");
    
        if (currentRendererWrapper) {
            auto remoteResolution = currentRendererWrapper->renderer->width.ToString() +
                                    "x" +
                                    currentRendererWrapper->renderer->height.ToString();
            _si_res2_->Text = remoteResolution;
        }
    }
    
    void
    VideoPage::updateCallTimer(Object^ sender, Object^ e)
    {
        if(auto item = SmartPanelItemsViewModel::instance->_selectedItem)
            _callTime_->Text = item->_callTime;
    }
    
    void
    VideoPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e)
    {
        updatePageContent();
        closeChatPanel();
    }
    
    void RingClientUWP::Views::VideoPage::updatePageContent()
    {
        auto item = SmartPanelItemsViewModel::instance->_selectedItem;
        auto contact = (item) ? item->_contact : nullptr;
    
        if (!contact)
            return;
    
        _contactName_->Text = contact->_bestName;
    
        //_contactBarAvatar_->ImageSource = RingClientUWP::ResourceMananger::instance->imageFromRelativePath(contact->_avatarImage);
        if (contact->_avatarImage != " ") {
            auto avatarImageUri = ref new Windows::Foundation::Uri(contact->_avatarImage);
            _contactBarAvatar_->ImageSource = ref new BitmapImage(avatarImageUri);
            _defaultContactBarAvatarGrid_->Visibility = VIS::Collapsed;
            _contactBarAvatarGrid_->Visibility = VIS::Visible;
        }
        else {
            _defaultContactBarAvatarGrid_->Visibility = VIS::Visible;
            _contactBarAvatarGrid_->Visibility = VIS::Collapsed;
            _defaultAvatar_->Fill = contact->_avatarColorBrush;
            _defaultAvatarInitial_->Text = Utils::getUpperInitial(contact->_bestName2);
        }
    
        _messagesList_->ItemsSource = contact->_conversation->_messages;
    
        scrollDown();
    }
    
    void
    VideoPage::updatePreviewFrameDimensions()
    {
        double aspectRatio = VideoManager::instance->captureManager()->aspectRatio();
    
        TransformGroup^ transforms = ref new TransformGroup();
    
        double scaleValue = 1 + userPreviewHeightModifier / _PreviewImage_->Height;
    
        scaleValue = std::max(std::min(1.75, scaleValue), 0.5);
    
        userPreviewHeightModifier = _PreviewImage_->Height * (scaleValue - 1);
    
        ScaleTransform^ scale = ref new ScaleTransform();
        scale->ScaleX = scaleValue;
        scale->ScaleY = scaleValue;
    
        TranslateTransform^ translate = ref new TranslateTransform();
        switch (quadrant)
        {
        case Quadrant::SE:
            translate->Y = -userPreviewHeightModifier;
            translate->X = translate->Y * aspectRatio;
            break;
        case Quadrant::SW:
            translate->Y = -userPreviewHeightModifier;
            translate->X = 0;
            break;
        case Quadrant::NW:
            translate->Y = 0;
            translate->X = 0;
            break;
        case Quadrant::NE:
            translate->Y = 0;
            translate->X = -userPreviewHeightModifier * aspectRatio;
            break;
        default:
            break;
        }
    
        transforms->Children->Append(scale);
        transforms->Children->Append(translate);
    
        _PreviewImage_->RenderTransform = transforms;
    
        _PreviewImageResizer_->RenderTransform = translate;
    
        arrangeResizer();
    }
    
    void RingClientUWP::Views::VideoPage::scrollDown()
    {
        _scrollView_->UpdateLayout();
        _scrollView_->ScrollToVerticalOffset(_scrollView_->ScrollableHeight);
    }
    
    void RingClientUWP::Views::VideoPage::screenVideo(bool state)
    {
        if (state) {
            Video::VideoManager::instance->rendererManager()->raiseClearRenderTarget();
            _callPaused_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
            _IncomingVideoImage_->Visibility = Windows::UI::Xaml::Visibility::Visible;
            _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Visible;
            _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Visible;
        } else {
            _callPaused_->Visibility = Windows::UI::Xaml::Visibility::Visible;
            _IncomingVideoImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
            _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
            _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        }
    }
    
    void
    RingClientUWP::Views::VideoPage::_sendBtn__Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
        sendMessage();
    }
    
    void
    RingClientUWP::Views::VideoPage::sendMessage()
    {
        if (auto item = SmartPanelItemsViewModel::instance->_selectedItem) {
            auto contact = item->_contact;
    
            auto txt = _messageTextBox_->Text;
    
            /* empty the textbox */
            _messageTextBox_->Text = "";
    
            if (!contact || txt->IsEmpty())
                return;
    
            RingD::instance->sendSIPTextMessage(txt);
            scrollDown();
        }
    }
    
    void RingClientUWP::Views::VideoPage::Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnCancel__Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
    }
    
    void RingClientUWP::Views::VideoPage::_btnHangUp__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        if (auto item = SmartPanelItemsViewModel::instance->_selectedItem) {
            _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
            _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
            RingD::instance->hangUpCall2(item->_callId);
            pressHangUpCall();
        }
        else
            WNG_("item not found, cannot hang up");
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnPause__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        auto item = SmartPanelItemsViewModel::instance->_selectedItem;
        if (item->_callStatus == CallStatus::IN_PROGRESS)
            RingD::instance->pauseCall(Utils::toString(item->_callId));
        else if (item->_callStatus == CallStatus::PAUSED)
            RingD::instance->unPauseCall(Utils::toString(item->_callId));
    
        pauseCall();
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnChat__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        if (!isChatPanelOpen) {
            openChatPanel();
        }
        else {
            closeChatPanel();
        }
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnAddFriend__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        addContactCall();
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnSwitch__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        transferCall();
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnMicrophone__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        switchMicrophoneStateCall();
        auto item = SmartPanelItemsViewModel::instance->_selectedItem;
    
        auto state = !item->_audioMuted;
        item->_audioMuted = state;
    
        // refacto : compare how video and audios are muted, then decide which solution is best.
        RingD::instance->muteAudio(Utils::toString(item->_callId), state); // nb : muteAudio == setMuteAudio
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnMemo__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        reccordVideoCall();
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnHQ__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        qualityVideoLevelCall();
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnVideo__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        switchVideoStateCall();
    }
    
    
    void RingClientUWP::Views::VideoPage::_videoControl__PointerMoved(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        if (barFading)
            fadeVideoControlsStoryboard->Begin();
    }
    
    
    void RingClientUWP::Views::VideoPage::btnAny_entered(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        barFading = false;
        fadeVideoControlsStoryboard->Stop();
    }
    
    
    void RingClientUWP::Views::VideoPage::btnAny_exited(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        barFading = true;
    }
    
    void
    VideoPage::SetBuffer(uint8_t* buf, int width, int height)
    {
        videoFrame = ref new VideoFrame(BitmapPixelFormat::Bgra8, width, height);
        softwareBitmap = videoFrame->SoftwareBitmap;
    
        BitmapBuffer^ bitmapBuffer = softwareBitmap->LockBuffer(BitmapBufferAccessMode::Write);
        IMemoryBufferReference^ memoryBufferReference = bitmapBuffer->CreateReference();
    
        Microsoft::WRL::ComPtr<IMemoryBufferByteAccess> byteAccess;
        if (SUCCEEDED(reinterpret_cast<IUnknown*>(memoryBufferReference)->QueryInterface(IID_PPV_ARGS(&byteAccess))))
        {
            byte* data;
            unsigned capacity;
            byteAccess->GetBuffer(&data, &capacity);
            std::memcpy(data, buf, static_cast<size_t>(capacity));
        }
        delete memoryBufferReference;
        delete bitmapBuffer;
    }
    
    
    task<void>
    VideoPage::WriteFrameAsSoftwareBitmapAsync(String^ id, uint8_t* buf, int width, int height)
    {
        SetBuffer(buf, width, height);
    
        VideoManager::instance->rendererManager()->renderer(id)->isRendering = false;
    
        auto sbSource = ref new Media::Imaging::SoftwareBitmapSource();
        return create_task(sbSource->SetBitmapAsync(softwareBitmap))
               .then([this, sbSource]()
        {
            try {
                _IncomingVideoImage_->Source = sbSource;
            }
            catch (Exception^ e) {
                EXC_(e);
            }
        });
    }
    
    
    void RingClientUWP::Views::VideoPage::OnincomingMessage(Platform::String ^callId, Platform::String ^payload)
    {
        openChatPanel();
        scrollDown();
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnVideo__Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
        auto item = SmartPanelItemsViewModel::instance->_selectedItem;
    
        item->muteVideo(!item->_videoMuted);
    }
    
    
    void RingClientUWP::Views::VideoPage::OnincomingVideoMuted(Platform::String ^callId, bool state)
    {
        /*_callPaused_->Visibility = (state)
                                       ? Windows::UI::Xaml::Visibility::Visible
                                       : Windows::UI::Xaml::Visibility::Collapsed;*/
    
        _IncomingVideoImage_->Visibility = (state)
                                         ? Windows::UI::Xaml::Visibility::Collapsed
                                         : Windows::UI::Xaml::Visibility::Visible;
    }
    
    
    void RingClientUWP::Views::VideoPage::OnstartPreviewing()
    {
        _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Visible;
        _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Visible;
    }
    
    
    void RingClientUWP::Views::VideoPage::OnstopPreviewing()
    {
        _PreviewImage_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        _PreviewImageResizer_->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    }
    
    
    void RingClientUWP::Views::VideoPage::_btnMicrophone__Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
        switchMicrophoneStateCall();
        auto item = SmartPanelItemsViewModel::instance->_selectedItem;
    
        auto state = !item->_audioMuted;
        item->_audioMuted = state;
    
        // refacto : compare how video and audios are muted, then decide which solution is best.
        RingD::instance->muteAudio(Utils::toString(item->_callId), state); // nb : muteAudio == setMuteAudio
    }
    
    
    void RingClientUWP::Views::VideoPage::OnaudioMuted(const std::string &callId, bool state)
    {
        _txbkMicrophoneMuted_->Visibility = (state) ? Windows::UI::Xaml::Visibility::Visible
                                            : Windows::UI::Xaml::Visibility::Collapsed;
    }
    
    
    void RingClientUWP::Views::VideoPage::OnvideoMuted(const std::string &callId, bool state)
    {
        _txbkVideoMuted_->Visibility = (state) ? Windows::UI::Xaml::Visibility::Visible
                                       : Windows::UI::Xaml::Visibility::Collapsed;
    }
    
    void RingClientUWP::Views::VideoPage::IncomingVideoImage_DoubleTapped(Platform::Object^ sender, Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs^ e)
    {
        RingD::instance->toggleFullScreen();
        anchorPreview();
    }
    
    void RingClientUWP::Views::VideoPage::InitManipulationTransforms()
    {
        PreviewImage_transforms = ref new TransformGroup();
        PreviewImage_previousTransform = ref new MatrixTransform();
        PreviewImage_previousTransform->Matrix = Matrix::Identity;
        PreviewImage_deltaTransform = ref new CompositeTransform();
    
        PreviewImage_transforms->Children->Append(PreviewImage_previousTransform);
        PreviewImage_transforms->Children->Append(PreviewImage_deltaTransform);
    
        _PreviewImageRect_->RenderTransform = PreviewImage_transforms;
    }
    
    void RingClientUWP::Views::VideoPage::PreviewImage_ManipulationDelta(Platform::Object^ sender, ManipulationDeltaRoutedEventArgs^ e)
    {
        if (!isMovingPreview)
            isMovingPreview = true;
    
        _PreviewImageRect_->RenderTransform = PreviewImage_transforms;
    
        PreviewImage_previousTransform->Matrix = PreviewImage_transforms->Value;
    
        PreviewImage_deltaTransform->TranslateX = e->Delta.Translation.X;
        PreviewImage_deltaTransform->TranslateY = e->Delta.Translation.Y;
    
        computeQuadrant();
    }
    
    void
    RingClientUWP::Views::VideoPage::computeQuadrant()
    {
        // Compute center coordinate of _videoContent_
        Point centerOfVideoFrame = Point(   static_cast<float>(_videoContent_->ActualWidth - _colChatBx_->ActualWidth) / 2,
                                            static_cast<float>(_videoContent_->ActualHeight - _rowChatBx_->ActualHeight) / 2  );
    
        // Compute the center coordinate of _PreviewImage_ relative to _videoContent_
        Point centerOfPreview = Point( static_cast<float>(_PreviewImage_->ActualWidth) / 2,
                                       static_cast<float>(_PreviewImage_->ActualHeight) / 2 );
        UIElement^ container = dynamic_cast<UIElement^>(VisualTreeHelper::GetParent(_videoContent_));
        GeneralTransform^ transform = _PreviewImage_->TransformToVisual(container);
        Point relativeCenterOfPreview = transform->TransformPoint(centerOfPreview);
    
        // Compute the difference between the center of _videoContent_
        // and the relative scaled center of _PreviewImageRect_
        Point diff = Point( centerOfVideoFrame.X - relativeCenterOfPreview.X,
                            centerOfVideoFrame.Y - relativeCenterOfPreview.Y    );
    
        lastQuadrant = quadrant;
        if (diff.X > 0)
            quadrant = diff.Y > 0 ? Quadrant::NW : Quadrant::SW;
        else
            quadrant = diff.Y > 0 ? Quadrant::NE : Quadrant::SE;
    
        if (lastQuadrant != quadrant) {
            arrangeResizer();
        }
    }
    
    void
    RingClientUWP::Views::VideoPage::arrangeResizer()
    {
        double scaleValue = (userPreviewHeightModifier + _PreviewImage_->Height) / _PreviewImage_->Height;
        float scaledWidth = static_cast<float>(scaleValue * _PreviewImage_->ActualWidth);
        float scaledHeight = static_cast<float>(scaleValue * _PreviewImage_->ActualHeight);
    
        float rSize = 20; // the size of the square UIElement used to resize the preview
        float xOffset, yOffset;
        PointCollection^ resizeTrianglePoints = ref new PointCollection();
        switch (quadrant)
        {
        case Quadrant::SE:
            xOffset = 0;
            yOffset = 0;
            resizeTrianglePoints->Append(Point(xOffset,         yOffset));
            resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset));
            resizeTrianglePoints->Append(Point(xOffset,         yOffset + rSize));
            break;
        case Quadrant::SW:
            xOffset = scaledWidth - rSize;
            yOffset = 0;
            resizeTrianglePoints->Append(Point(xOffset,         yOffset));
            resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset));
            resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset + rSize));
            break;
        case Quadrant::NW:
            xOffset = scaledWidth - rSize;
            yOffset = scaledHeight - rSize;
            resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset));
            resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset + rSize));
            resizeTrianglePoints->Append(Point(xOffset,         yOffset + rSize));
            break;
        case Quadrant::NE:
            xOffset = 0;
            yOffset = scaledHeight - rSize;
            resizeTrianglePoints->Append(Point(xOffset,         yOffset + rSize));
            resizeTrianglePoints->Append(Point(xOffset + rSize, yOffset + rSize));
            resizeTrianglePoints->Append(Point(xOffset,         yOffset));
            break;
        default:
            break;
        }
        _PreviewImageResizer_->Points = resizeTrianglePoints;
    }
    
    void RingClientUWP::Views::VideoPage::PreviewImage_ManipulationCompleted(Platform::Object^ sender, ManipulationCompletedRoutedEventArgs^ e)
    {
        isMovingPreview = false;
        anchorPreview();
        updatePreviewFrameDimensions();
    }
    
    void
    VideoPage::PreviewImageResizer_Pressed(Object^ sender, PointerRoutedEventArgs^ e)
    {
        isResizingPreview = true;
        lastUserPreviewHeightModifier = userPreviewHeightModifier;
    }
    
    void RingClientUWP::Views::VideoPage::anchorPreview()
    {
        PreviewImage_previousTransform->Matrix = Matrix::Identity;
        _PreviewImageRect_->RenderTransform =  nullptr;
    
        switch (quadrant)
        {
        case Quadrant::SE:
            _PreviewImageRect_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Right;
            _PreviewImageRect_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Bottom;
            break;
        case Quadrant::SW:
            _PreviewImageRect_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Left;
            _PreviewImageRect_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Bottom;
            break;
        case Quadrant::NW:
            _PreviewImageRect_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Left;
            _PreviewImageRect_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Top;
            break;
        case Quadrant::NE:
            _PreviewImageRect_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Right;
            _PreviewImageRect_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Top;
            break;
        default:
            break;
        };
    }
    
    void
    VideoPage::PreviewImageResizer_ManipulationDelta(Platform::Object^ sender, ManipulationDeltaRoutedEventArgs^ e)
    {
        if (!isResizingPreview)
            isResizingPreview = true;
    
        if (quadrant == Quadrant::NW || quadrant == Quadrant::NE) {
            userPreviewHeightModifier = lastUserPreviewHeightModifier + e->Cumulative.Translation.Y;
        }
        else {
            userPreviewHeightModifier = lastUserPreviewHeightModifier - (e->Cumulative.Translation.Y);
        }
    
        updatePreviewFrameDimensions();
    }
    
    void
    VideoPage::PreviewImageResizer_ManipulationCompleted(Platform::Object^ sender, ManipulationCompletedRoutedEventArgs^ e)
    {
        isResizingPreview = false;
        if (!isHoveringOnResizer) {
            CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0);
        }
    }
    
    void
    VideoPage::PreviewImage_PointerReleased(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        // For some reason, PreviewImage_ManipulationCompleted doesn't always fired when the mouse is released
        anchorPreview();
        updatePreviewFrameDimensions();
    }
    
    void
    VideoPage::PreviewImageResizer_PointerEntered(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        isHoveringOnResizer = true;
        if (!isMovingPreview) {
            switch (quadrant)
            {
            case Quadrant::SE:
            case Quadrant::NW:
                CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::SizeNorthwestSoutheast, 0);
                break;
            case Quadrant::SW:
            case Quadrant::NE:
                CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::SizeNortheastSouthwest, 0);
                break;
            default:
                break;
            }
        }
    }
    
    void
    VideoPage::PreviewImageResizer_PointerExited(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        isHoveringOnResizer = false;
        if (!isResizingPreview) {
            CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0);
        }
    }
    
    void
    VideoPage::PreviewImageResizer_PointerReleased(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        if (!isHoveringOnResizer) {
            CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0);
        }
    }
    
    void
    VideoPage::_chatPanelResizeBarGrid__PointerEntered(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        if (chtBoxOrientation == Orientation::Horizontal) {
            CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::SizeWestEast, 0);
        }
        else {
            CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::SizeNorthSouth, 0);
        }
    }
    
    void
    VideoPage::_chatPanelResizeBarGrid__PointerExited(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        if (!isResizingChatPanel) {
            CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0);
        }
    }
    
    void
    VideoPage::_chatPanelResizeBarGrid__PointerPressed(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        isResizingChatPanel = true;
        lastchatPanelSize = chatPanelSize;
    }
    
    void
    VideoPage::_chatPanelResizeBarGrid__PointerReleased(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e)
    {
        isResizingChatPanel = false;
    }
    
    void
    VideoPage::_chatPanelResizeBarGrid__ManipulationDelta(Platform::Object^ sender, ManipulationDeltaRoutedEventArgs^ e)
    {
        if (chtBoxOrientation == Orientation::Horizontal) {
            chatPanelSize = lastchatPanelSize - e->Cumulative.Translation.X;
        }
        else {
            chatPanelSize = lastchatPanelSize - e->Cumulative.Translation.Y;
        }
        resizeChatPanel();
    }
    
    void
    VideoPage::_chatPanelResizeBarGrid__ManipulationCompleted(Platform::Object^ sender, ManipulationCompletedRoutedEventArgs^ e)
    {
        isResizingChatPanel = false;
        CoreApplication::MainView->CoreWindow->PointerCursor = ref new Windows::UI::Core::CoreCursor(Windows::UI::Core::CoreCursorType::Arrow, 0);
    }
    
    void
    VideoPage::openChatPanel()
    {
        resizeChatPanel();
        isChatPanelOpen = true;
        SmartPanelItemsViewModel::instance->_selectedItem->_contact->_unreadMessages = 0;
    }
    void
    VideoPage::closeChatPanel()
    {
        _colChatBx_->Width = 0;
        _rowChatBx_->Height = 0;
        isChatPanelOpen = false;
    }
    
    void
    VideoPage::resizeChatPanel()
    {
        // clamp chatPanelSize
        double minChatPanelSize = 176;
        double maxChatPanelSize = chtBoxOrientation == Orientation::Horizontal ?
            _videoContent_->ActualWidth * .5 :
            _videoContent_->ActualHeight * .5;
        chatPanelSize = std::max(minChatPanelSize, std::min(chatPanelSize, maxChatPanelSize));
    
        if (chtBoxOrientation == Orientation::Horizontal) {
            _colChatBx_->Width = GridLength(chatPanelSize, GridUnitType::Pixel);
            _rowChatBx_->Height = 0;
        }
        else {
            _colChatBx_->Width = 0;
            _rowChatBx_->Height = GridLength(chatPanelSize, GridUnitType::Pixel);
        }
    }
    
    void
    VideoPage::_btnToggleOrientation__Tapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e)
    {
        bool wasChatPanelOpen = isChatPanelOpen;
        closeChatPanel();
    
        if (chtBoxOrientation == Orientation::Horizontal) {
            chtBoxOrientation = Orientation::Vertical;
            _btnToggleOrientation_->Content = L"\uE90D";
    
            _chatPanelResizeBarGrid_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Stretch;
            _chatPanelResizeBarGrid_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Top;
            _chatPanelResizeBarGrid_->Width = std::numeric_limits<double>::quiet_NaN();
            _chatPanelResizeBarGrid_->Height = 4;
    
            Grid::SetColumn(_chatPanelGrid_, 0);
            Grid::SetRow(_chatPanelGrid_, 2);
        }
        else {
            chtBoxOrientation = Orientation::Horizontal;
            _btnToggleOrientation_->Content = L"\uE90E";
    
            _chatPanelResizeBarGrid_->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Left;
            _chatPanelResizeBarGrid_->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Stretch;
            _chatPanelResizeBarGrid_->Width = 4;
            _chatPanelResizeBarGrid_->Height = std::numeric_limits<double>::quiet_NaN();
    
            Grid::SetColumn(_chatPanelGrid_, 2);
            Grid::SetRow(_chatPanelGrid_, 0);
        }
    
        if (wasChatPanelOpen)
            openChatPanel();
    }
    
    void
    VideoPage::_videoContent__SizeChanged(Platform::Object^ sender, Windows::UI::Xaml::SizeChangedEventArgs^ e)
    {
        if (isChatPanelOpen)
            resizeChatPanel();
    }
    
    void
    VideoPage::_messageTextBox__TextChanged(Platform::Object^ sender, TextChangedEventArgs^ e)
    {
        bool carriageReturnPressed = false;
        if (_messageTextBox_->Text->Length() == (lastMessageText->Length() + 1) &&
            _messageTextBox_->Text != "\r") {
            unsigned cursorPos = 0;
            auto strMessage = Utils::toString(_messageTextBox_->Text);
            auto strLastMessage = Utils::toString(lastMessageText);
            for (std::string::size_type i = 0; i < strLastMessage.size(); ++i) {
                if (strMessage[i] != strLastMessage[i]) {
                    auto changed = strMessage.substr(i, 1);
                    if (changed == "\r") {
                        carriageReturnPressed = true;
                        MSG_("CR inside");
                    }
                    break;
                }
            }
    
            if (strMessage.substr(strMessage.length() - 1) == "\r") {
                if (lastMessageText->Length() != 0) {
                    carriageReturnPressed = true;
                    MSG_("CR at end");
                }
            }
        }
    
        if (carriageReturnPressed && !(RingD::instance->isCtrlPressed || RingD::instance->isShiftPressed)) {
            _messageTextBox_->Text = lastMessageText;
            sendMessage();
            lastMessageText = "";
        }
    
        DependencyObject^ child = VisualTreeHelper::GetChild(_messageTextBox_, 0);
        auto sendBtnElement = Utils::xaml::FindVisualChildByName(child, "_sendBtn_");
        auto sendBtn = dynamic_cast<Button^>(sendBtnElement);
        if (_messageTextBox_->Text != "") {
            sendBtn->IsEnabled = true;
        }
        else {
            sendBtn->IsEnabled = false;
        }
    
        lastMessageText = _messageTextBox_->Text;
    }
    
    void
    VideoPage::_corewindow__KeyDown(CoreWindow^ sender, KeyEventArgs^ e)
    {
        auto ctrlState = CoreWindow::GetForCurrentThread()->GetKeyState(VirtualKey::Control);
        auto isCtrlDown = ctrlState != CoreVirtualKeyStates::None;
        if (isCtrlDown && e->VirtualKey == Windows::System::VirtualKey::I) {
            _smartInfoBorder_->Visibility = _smartInfoBorder_->Visibility == VIS::Collapsed ? VIS::Visible : VIS::Collapsed;
        }
    }