Monday, January 6, 2020

Universal Windows Platform – 360View App

360View App shows to create an application which will allow you to Open and Play 360 Video also known as Spherical Video Files

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.
vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…
vs2017-file-new-project

Step 3

From New Project choose Visual C# from InstalledTemplates then choose Blank App (Universal Windows) and then type in a Name and select a Location and then select Ok to create the Project
vs2017-new-project-window

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.
vs2017-target-platform
The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…
vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project
vs2017-add-new-item-library

Step 7

Once in the Code View for Library.cs the following should be entered:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
using System;
using System.Numerics;
using Windows.Media.Core;
using Windows.Media.MediaProperties;
using Windows.Media.Playback;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
 
public class Library
{
    private const int scroll_step = 2;
    private const int mouse_wheel = 120;
    private const double change_increment = 0.5;
 
    private MediaPlaybackSphericalVideoProjection _projection;
    private MediaPlayerElement _element = null;
    private MediaPlayer _player;
    private Grid _grid = null;
    private bool _press = false;
    private double _delta = 1.8f;
    private double _centerX = 0;
    private double _centerY = 0;
 
    private Quaternion GetPitchRoll(double heading, double pitch, double roll)
    {
        double ToRadians(double degree)
        {
            return degree * Math.PI / 180.0;
        }
        Quaternion result;
        double headingPart = ToRadians(heading) * change_increment;
        double sin1 = Math.Sin(headingPart);
        double cos1 = Math.Cos(headingPart);
        double pitchPart = ToRadians(-pitch) * change_increment;
        double sin2 = Math.Sin(pitchPart);
        double cos2 = Math.Cos(pitchPart);
        double rollPart = ToRadians(roll) * change_increment;
        double sin3 = Math.Sin(rollPart);
        double cos3 = Math.Cos(rollPart);
        result.W = (float)(cos1 * cos2 * cos3 - sin1 * sin2 * sin3);
        result.X = (float)(cos1 * cos2 * sin3 + sin1 * sin2 * cos3);
        result.Y = (float)(sin1 * cos2 * cos3 + cos1 * sin2 * sin3);
        result.Z = (float)(cos1 * sin2 * cos3 - sin1 * cos2 * sin3);
        return result;
    }
 
    private bool IsOpened()
    {
        if (_player != null && _projection != null &&
        _player.PlaybackSession.PlaybackState != MediaPlaybackState.Opening &&
        _player.PlaybackSession.PlaybackState != MediaPlaybackState.None) return true;
        return false;
    }
 
    private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
    {
        if (e.OriginalSource != _grid && _press)
        {
            double changeX = e.GetCurrentPoint(_element).Position.X - _centerX;
            double changeY = _centerY - e.GetCurrentPoint(_element).Position.Y;
            _projection.ViewOrientation = GetPitchRoll(changeX, changeY, 0);
        }
        e.Handled = true;
    }
 
    private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
    {
        _press = false;
        e.Handled = true;
    }
 
    private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        if (e.OriginalSource != _grid && IsOpened()) _press = true;
        e.Handled = true;
    }
 
    private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e)
    {
        if (e.OriginalSource != _grid)
        {
            double value = _projection.HorizontalFieldOfViewInDegrees +
                (scroll_step * e.GetCurrentPoint(_element).Properties.MouseWheelDelta / mouse_wheel);
            if (value > 0 && value <= 180)
            {
                _projection.HorizontalFieldOfViewInDegrees = value;
            }
        }
        e.Handled = true;
    }
 
    private void PlaybackSession_PlaybackStateChanged(MediaPlaybackSession sender, object args)
    {
        _delta = (_player.PlaybackSession.PlaybackState ==
        MediaPlaybackState.Playing) ? 1.8f : 0.25f;
    }
 
    private void Player_MediaOpened(MediaPlayer sender, object args)
    {
        _projection = _player.PlaybackSession.SphericalVideoProjection;
        SphericalVideoFrameFormat videoFormat = _projection.FrameFormat;
        if (videoFormat != SphericalVideoFrameFormat.Equirectangular)
        {
            _projection.FrameFormat = SphericalVideoFrameFormat.Equirectangular;
        }
        _projection.IsEnabled = true;
        _projection.HorizontalFieldOfViewInDegrees = 120;
        _player.PlaybackSession.PlaybackStateChanged += PlaybackSession_PlaybackStateChanged;
    }
 
    public void Layout(ref MediaPlayerElement display)
    {
        if(_element == null) _element = display;
        _centerX = _element.ActualWidth / 2;
        _centerY = _element.ActualHeight / 2;
        if (_grid == null)
        {
            FrameworkElement _root = (FrameworkElement)VisualTreeHelper.GetChild(_element.TransportControls, 0);
            if (_root != null)
            {
                _grid = (Grid)_root.FindName("ControlPanelGrid");
                _grid.PointerPressed += OnPointerPressed;
                _grid.PointerReleased += OnPointerReleased;
                _grid.PointerWheelChanged += OnPointerWheelChanged;
                _element.PointerPressed += OnPointerPressed;
                _element.PointerReleased += OnPointerReleased;
                _element.PointerMoved += OnPointerMoved;
                _element.PointerWheelChanged += OnPointerWheelChanged;
            }
        }
    }
 
    public async void Open(MediaPlayerElement display)
    {
        try
        {
            FileOpenPicker picker = new FileOpenPicker()
            {
                SuggestedStartLocation = PickerLocationId.PicturesLibrary
            };
            picker.FileTypeFilter.Add(".mp4");
            StorageFile open = await picker.PickSingleFileAsync();
            if (open != null)
            {
                if (_element == null) _element = display;
                _player = new MediaPlayer
                {
                    Source = MediaSource.CreateFromStorageFile(open)
                };
                _player.MediaOpened += Player_MediaOpened;
                _element.SetMediaPlayer(_player);
            }
        }
        finally
        {
            // Ignore Exceptions
        }
    }
}
In the Code File for Library there are using statements to include the necessary functionality and in the Library Class has some const values which will help with displaying the Spherical Video and private Members for MediaPlaybackSphericalVideoProjectionMediaPlayerElement and MediaPlayer along with other values which are used as part of navigating the Spherical Video during playback.
In the Library Class there is a GetPitchRoll Method this will be used to calculate the Quaternion Vector based on a passed in headingpitch and roll. The IsOpened Method is used to determine the MediaPlaybackState of the MediaPlayer as being Opened or Not. The OnPointerMoved Event Handler will be used to set the ViewOrientation of the MediaPlaybackSphericalVideoProjection using the GetPitchRoll Method.
Also in the Library Class there are Event Handlers for OnPointerMoved – which reacts to Mouse Movements or Touch Movements on the GridOnPointerReleased which will be when nothing is being Pressed and OnPointerReleased which is when an input is being Pressed and sets the _press Value accordingly. The PointerWheelChanged Event Handler reacts to Mouse Scroll Wheel Events to set the HorizontalFieldOfViewInDegrees of the MediaPlaybackSphericalVideoProjection, the PlaybackSession_PlaybackStateChanged Event Handler will set the relevant _delta Value and Player_MediaOpened will bv triggered when the MediaPlayer Video has been Opened, this will configure the look-and-feel of the Playback Session.
Still in the Library Class there is a Layout Method which is used to get the Grid from the MediaPlayerElement to allow interactions on top of it and will set all the Event Handlers to this and there is an Open Method which has a MediaPlayerElement Parameter passed in, within a try and catch Block to help handle any Exception there is a FileOpenPicker which is used to select a mp4 File it the sets the Source of the MediaPlayer using CreateFromStorageFile and sets up the MediaOpened Event Handler and the calls the SetMediaPlayer Method on the MediaPlayerElement of the MediaPlayer.

Step 8

In the Solution Explorer select MainPage.xaml
vs2017-mainpage-library

Step 9

From the Menu choose View and then Designer
vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in in this between the Grid and /Grid elements, enter the following XAML:
1
2
3
4
5
<MediaPlayerElement Name="Display" AreTransportControlsEnabled="True"
AutoPlay="True" LayoutUpdated="Display_LayoutUpdated"/>
<CommandBar VerticalAlignment="Bottom">
    <AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click"/>
</CommandBar>
Within the main Grid Element, the first block of XAML is an MediaPlayerElement Control. The second block of XAML is a CommandBar with AppBarButton for Open which calls Open_Click.

Step 11

From the Menu choose View and then Code
vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:
1
2
3
4
5
6
7
8
9
10
11
Library library = new Library();
 
private void Display_LayoutUpdated(object sender, object e)
{
    library.Layout(ref Display);
}
 
private void Open_Click(object sender, RoutedEventArgs e)
{
    library.Open(Display);
}
There is a Display_LayoutUpdated Event Handler which will be triggered when the Layout of the Window is Updated and an Open_Click Event Handler which will will call the Open Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application
vs2017-local-machine

Step 14

Once the Application has started running you can tap on the Open Button then you need to select an mp4 Video File that is a Spherical Video Format which will then be displayed within the MediaPlayerElement Control and can be looked-around in with Mouse, Pen or Touch input
uwp-ran-360view-app
You can use the following sample Spherical Video File

Step 15

To Exit the Application select the Close button in the top right of the Application