/************************************************************************** | |
* Copyright (C) 2016 by Savoir-faire Linux * | |
* Author: Jäger 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; | |
} | |
} |