×
Namespaces

Variants
Actions

Creating an Interesting Places application - take and share media in WP7

From Nokia Developer Wiki
Jump to: navigation, search

This article explains how take and capture different media from the Interesting Places in Windows Phone 7. These Interesting Places with media files can be shared to the server side for other people use. Location of the stored Interesting Place can also be viewed in the Map control.

SignpostIcon HereMaps 99.png
SignpostIcon WP7 70px.png
Article Metadata
Code ExampleTested with
Devices(s): Nokia Lumia 800
Compatibility
Platform(s): Windows Phone 7.5
Windows Phone 7.5
Article
Keywords: WP7, Windows Phone, Image, Take Image, Audio, Record Audio, Video, Record Video, Isolated Storage, Streaming, Server, PHP, MySQL, SilverLight Toolkit, SimpleJSON, RestSharp
Created: pasi.manninen (24 May 2012)
Last edited: hamishwillee (26 Jun 2013)

Contents

Introduction

This code example demonstrate how to take a image, record audio or video and save these captured media files to Isolated Storage with location information. These Interesting Places with media files can be later viewed and shared to server side. This code example uses PHP server as a back end of this application. All captured media files are send 500 kB chucks with RestSharp and parsed together in server side with PHP. This application is tested with 5 min recorded video (for example), then video file size was 55 MB and sent chucks count was more than 110. Sent was tested over normal cellular network and sending takes about 8 minutes. It worked really nice in Windows Phone 7 without Background Agents.

Pivot Page contains My Places, Shared Places and settings

My Places Shared Places Settings

Interesting Place data and location is displayed in individual pages

Interesting Place Interesting Place in map

A new Interesting Place can be added with one page and UserControl (which displays uploading process)

Add a new Interesting Place Uploading a new Interesting Place to server

Here is a small demo video, which shows how the application works: <mediaplayer>http://www.youtube.com/watch?v=fDtxP9wrRAg</mediaplayer>


Back end of this application : MySQL and PHP

This application uses MySQL to store Interesting Places to server side. Use your own server to create Interesting Places table as described in below.

mysql> desc InterestingPlaces;
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | text | YES | | NULL | |
| text | text | YES | | NULL | |
| date | text | YES | | NULL | |
| image | text | YES | | NULL | |
| audio | text | YES | | NULL | |
| video | text | YES | | NULL | |
| latitude | text | YES | | NULL | |
| longitude | text | YES | | NULL | |
| nickname | text | YES | | NULL | |
+-----------+---------+------+-----+---------+----------------+
10 rows in set (0.00 sec)
 
mysql>

Needed PHP-files are described later in this code example (when they are used from Windows Phone application). Big thanks goes to Juha Peltomäki who helped me with PHP files to store and loading data from MySQL.

Here is the needed folder structure in server side (with PHP files). Remember add write access to files folder (image, audio and video files will be saved there).

InterestingPlace/addData.php
InterestingPlace/addFile.php
InterestingPlace/getData.php
InterestingPlace/files

Windows Phone 7.1 SDK

To Develop application for Windows Phone 7 devices, you need to install Windows Phone 7.1 SDK. You can download latest SDK for Windows Phone here.

Windows Phone Application

To start creating a new Windows Phone application, start Microsoft Visual Studio then create a new Project and select Windows Phone Application Template. Pivot Items will be created in the code.

Create a new project

This example uses C# as code behind language.

Project extensions

Microsoft Visual Studio extensions can be installed in many different ways. This article uses Nuget which makes extension installation easier.

Use Nuget, find and install following extension:

  • Silverlight Toolkit
  • Simple JSON
  • RestSharp


Silverlight Toolkit is be used to get TiltEffect working in ListBoxes. Simple JSON is used when Interesting Places data is loaded from the server side. All data from this application is send to server with using RestSharp.

You will also need to install PhonePerformance, which is used to load images from server when shared Interesting Places are loaded. Look more detail about installing Nuget and PhonePerformance here.

Classes behind the solution : Place and PlaceListItem

Place class is used to store one Interesting Place. This class is pure data class and it is not used in UI. These objects will be saved to Isolated Storage.

public class Place
{
public string Title { get; set; }
public string Text { get; set; }
public string Image { get; set; }
public int ImageWidth { get; set; }
public int ImageHeight { get; set; }
public string Audio { get; set; }
public string Video { get; set; }
public string Date { get; set; }
public string Latitude { get; set; }
public string Longitude { get; set; }
public bool IsShared { get; set; }
public string Nickname { get; set; }
}

PlaceListItem class is used when data is binded to the ListBoxes in the screen. The main difference between these classes is ImageSource which is filled and loaded dynamically from the Isolated Storage or from server and this way image data is binded to List Item.

public class PlaceListItem
{
public ImageSource ImageSource { get; set; }
public string Title { get; set; }
public string Date { get; set; }
public string ImageURL { get; set; }
public string Nickname { get; set; }
}

Handling Interesting Places data between different Pages

This code example has a few different pages: Pivot page (my places, shared places and settings), add a new Interesting Place page, Play and Record Video page, Show Map page and Show Interesting Place page. One of the easiest way to share data between these pages in Windows Phone, is to store all data to App.xaml.cs class. One other advantage to store all the data to same place is when you have to handle Tombstone mode in Windows Phone.

This code example doesn't contains any code to handle Tombstoning mode, you can find information about Thombstoning for example here.

Programming (App.xaml.cs)

In App.xaml.cs class all the album data is stored to the List collections. There is also selectedList variable to store selected list (MyPlaces or SharedPlaces), one varible to handle a new place added situation and one varible to store user's nickname.

// places
public List<Place> MyPlaces = new List<Place>();
public List<Place> SharedPlaces = new List<Place>();
// selected lisg - MyPlaces or SharedPlaces
public string SelectedList = "";
// a new place is added
public bool IsPlaceAdded = false;
// my nickname
public string nickname;

Application loads all of the those lists data from the Isolated Storage when the application is launched. Application_Launching method will be executed in Windows Phone when the application is launched.

private void Application_Launching(object sender, LaunchingEventArgs e)
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
// MyPlaces.dat
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("MyPlaces.dat", FileMode.OpenOrCreate, isoStore))
{
if (stream.Length > 0)
{
try
{
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Place>));
MyPlaces = serializer.ReadObject(stream) as List<Place>;
}
catch (SerializationException)
{
MessageBox.Show("Error loading My Places data from Isolated Storage!");
}
}
}
// nickname
IsolatedStorageSettings appSettings;
appSettings = IsolatedStorageSettings.ApplicationSettings;
if (appSettings.Contains("nickname"))
{
nickname = (string)appSettings["nickname"];
}
}

Application saves all of those lists data to the Isolated Storage when the application is closing. Nickname will be saved when it was changed from Pivot Page. Application_Closing method will be executed in Windows Phone when the application is closing.

private void Application_Closing(object sender, ClosingEventArgs e)
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
// MyPlaces.dat
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("MyPlaces.dat", FileMode.Create, isoStore))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Place>));
serializer.WriteObject(stream, MyPlaces);
}
}

The Main Page of the application : Pivot controls

This application uses Pivot as a main page of the application. Here in the Pivot page user can see his/her own and shared Interesting Places in two different lists. User can also set or change his/her nickname here in Settings Pivot. Nickname has to be set before user can share Interesting Places to server side.

Note: You have to add reference to Microsoft.Phone.Controls to use Pivot Controls.

My Places Shared Places Settings

Design (MainPage.xaml)

This Pivot page uses two different lists as a Pivot controls. These lists data are binded from the PlaceListItem class.

My Places List shows the user own not shared Interesting Place as in a one row in the MyPlacesListBox. List item template is described in App.xaml as a ItemTemplate, which contains a grid with image, title and date. Shared Places List is almost the same but it also includes nickame in ItemTemplate.

<Grid x:Name="LayoutRoot" Background="Transparent">
<controls:Pivot Title="INTERESTING PLACES" x:Name="PivotControl">
<controls:PivotItem Header="my places">
<ListBox x:Name="MyPlacesListBox"
toolkit:TiltEffect.IsTiltEnabled="True"
ItemTemplate="{StaticResource MyPlaceDataTemplate}"
SelectionChanged="MyPlacesListBox_SelectionChanged"/>
</controls:PivotItem>
<controls:PivotItem Header="shared places">
<Grid>
<ListBox x:Name="SharedPlacesListBox"
toolkit:TiltEffect.IsTiltEnabled="True"
ItemTemplate="{StaticResource SharedPlaceDataTemplate}"
SelectionChanged="SharedPlacesListBox_SelectionChanged" Height="451" Margin="0,0,0,84" />
<Button Content="Load Shared Places" Height="72" HorizontalAlignment="Left" Margin="7,457,0,0" Name="LoadSharedPlacesButton" VerticalAlignment="Top" Width="450" Click="LoadSharedPlacesButton_Click" />
</Grid>
</controls:PivotItem>
<controls:PivotItem Header="settings">
<Grid>
<TextBlock Height="30" HorizontalAlignment="Left" Margin="8,25,0,0" Name="textBlock1" Text="Nickname:" VerticalAlignment="Top" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="110,5,0,0" Name="NicknameTextBox" Text="" VerticalAlignment="Top" Width="339" />
<Button Content="Save" Height="72" HorizontalAlignment="Left" Margin="110,139,0,0" Name="SaveNicknameButton" VerticalAlignment="Top" Width="160" Click="SaveNicknameButton_Click" />
<TextBlock Height="60" HorizontalAlignment="Left" Margin="122,82,0,0" Name="textBlock2" Text="You have to use nickname before you can share places." VerticalAlignment="Top" FontSize="16" FontStyle="Italic" Width="314" TextWrapping="Wrap" />
</Grid>
</controls:PivotItem>
</controls:Pivot>
</Grid>

Application resources in App.xaml has data templates for the list boxes item templates.

<Application.Resources>
<DataTemplate x:Key="MyPlaceDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Margin="5" BorderBrush="Black" BorderThickness="1">
<Image Source="{Binding ImageSource}" Width="150" Height="110"/>
</Border>
<StackPanel Grid.Column="1" Margin="5">
<TextBlock Text="{Binding Title}" FontSize="24" />
<TextBlock Text="{Binding Date}" FontSize="18" FontStyle="Italic" />
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SharedPlaceDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Margin="5" BorderBrush="Black" BorderThickness="1">
<Image delay:LowProfileImageLoader.UriSource="{Binding ImageURL}" Width="150" Height="110"/>
</Border>
<StackPanel Grid.Column="1" Margin="5">
<TextBlock Text="{Binding Title}" FontSize="24" />
<TextBlock Text="{Binding Date}" FontSize="18" FontStyle="Italic"/>
<TextBlock Text="{Binding Nickname}" FontSize="18" FontStyle="Italic"/>
</StackPanel>
</Grid>
</DataTemplate>
</Application.Resources>

This application has one ApplicationBarIconButton which is used to add a new Interesting Place. You can copy application bar icons to your application from SDK installation folder.

<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="False">
<shell:ApplicationBarIconButton IconUri="/Images/appbar.new.rest.png" Text="Add" Click="ApplicationBarAddIconButton_Click"/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="Add"/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

Programming (MainPage.xaml.cs)

A few variables are described here in the Main page. Reference to App class and lists to hold data to bind ListBoxes.

// Reference to App
private App app = App.Current as App;
// My Places list in UI
private List<PlaceListItem> myPlaces = new List<PlaceListItem>();
private List<PlaceListItem> sharedPlaces = new List<PlaceListItem>();

Only users own Interesting Places will be loaded when the application is launched, this happens when application is navigated to this page.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// nothing is selected in lists
MyPlacesListBox.SelectedIndex = -1;
SharedPlacesListBox.SelectedIndex = -1;
 
// create PlaceListItem objects from MyList items, if a new place is added
if (app.IsPlaceAdded == true || e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
{
CreateMyPlacesItems();
app.IsPlaceAdded = false;
}
 
// show nickname in title and settings
PivotControl.Title = "INTERESTING PLACES - " + app.nickname;
NicknameTextBox.Text = app.nickname;
}

CreateMyPlacesItems method goes through all the places data and generates a object from PlaceListItem class. This class stores place title, date and loads image data from the Isolated Storage where all the data of my Interested Places are stored. Finally data is binded to MyPlacesListBox.

private void CreateMyPlacesItems()
{
// empty previous my places
myPlaces.Clear();
// create PlaceListItem objects
foreach (Place place in app.MyPlaces)
{
// create a new item
PlaceListItem item = new PlaceListItem();
// title
item.Title = place.Title;
// date
item.Date = place.Date;
// read image data from Isolated Storage
if (place.Image != null)
{
BitmapImage imageFromStorage = new BitmapImage();
using (var isoFile = IsolatedStorageFile.GetUserStoreForApplication())
{
using (var imageStream = isoFile.OpenFile(place.Image, FileMode.Open, FileAccess.Read))
{
imageFromStorage.SetSource(imageStream);
}
}
item.ImageSource = imageFromStorage;
}
// add item to myplaces
myPlaces.Add(item);
}
// set data to UI
MyPlacesListBox.ItemsSource = null;
MyPlacesListBox.ItemsSource = myPlaces;
MyPlacesListBox.SelectedIndex = -1;
}

Now all the users own Interesting Places should be visible in the first Pivot control. Shared Interesting Places will be loaded and populated when user clicks LoadSharedPlacesButton in the screen.

private void LoadSharedPlacesButton_Click(object sender, RoutedEventArgs e)
{
LoadSharedPlacesDataFromServer();
}

LoadSharedPlacesDataFromServer method use WebClient class to connect to server side.

private void LoadSharedPlacesDataFromServer() 
{
WebClient webClient = new WebClient();
Uri uri = new Uri("http://YOUR OWN SERVER IP HERE/InterestingPlaces/getData.php);
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(JSONDataDownloaded);
webClient.DownloadStringAsync(uri);
}

In server side PHP first connects to database, selects all the Interesting Places and retuns JSON data (getData.php).

// Created by Juha Peltomäki
<?php
// user information
$dbHost = "localhost";
$dbUser = "username";
$dbPass = "password";
$dbName = "database";
 
// connect database
$mysqli = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
if (mysqli_connect_errno()) { echo mysqli_connect_error(); }
 
// create query
$select_sql = "SELECT *FROM InterestingPlaces ORDER BY date DESC";
$result = $mysqli->query($select_sql) or die($mysqli->error);
 
// create data array
$result_array = array();
while ($row = $result->fetch_assoc()) {
$result_array[] = $row;
}
 
// create JSON
echo json_encode(array('places'=>$result_array));
?>

In Windows Phone application, JSONDataDownloaded method will be called when JSON data is returned from the server side. First JSON string will be deserialized to dynamic object and a new place object will be created from JSON data.

private void JSONDataDownloaded(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Result == null || e.Error != null)
{
MessageBox.Show("Cannot get Interesting Places data from server!");
return;
}
// clear old list
app.SharedPlaces.Clear();
// Deserialize JSON string to dynamic object
IDictionary<string, object> json = (IDictionary<string, object>)SimpleJson.DeserializeObject(e.Result);
// places List
IList placesData = (IList)json["places"];
// find places
for (int i = 0; i < placesData.Count; i++)
{
// create a new place
Place place = new Place();
// place object
IDictionary<string, object> placeData = (IDictionary<string, object>)placesData[i];
place.Title = (string)placeData["title"];
place.Text = (string)placeData["text"];
place.Date = (string)placeData["date"];
place.Image = (string)placeData["image"];
place.Video = (string)placeData["video"];
place.Audio = (string)placeData["audio"];
place.Latitude = (string)placeData["latitude"];
place.Longitude = (string)placeData["longitude"];
place.Nickname = (string)placeData["nickname"];
// add place to SharedPlaces
app.SharedPlaces.Add(place);
}
// create shared places items to UI
CreateSharedPlacesItems();
}

CreateSharedPlacesItems method will be called after all the JSON data is parsed. This is almost the same process what is used with users own Interesting Places except here ImageURL is used instead of images data from the Isolated Storage.

private void CreateSharedPlacesItems()
{
// clear previous list data
sharedPlaces.Clear();
foreach (Place place in app.SharedPlaces)
{
// create a new item
PlaceListItem item = new PlaceListItem();
// title
item.Title = place.Title;
// date
item.Date = place.Date;
// nickname
item.Nickname = place.Nickname;
// set the image url
if (place.Image != null) {
item.ImageURL = "http://YOUR OWN SERVER IP HERE/InterestingPlaces/files/" + place.Image;
}
// add item to myplaces
sharedPlaces.Add(item);
}
// set data to UI
SharedPlacesListBox.ItemsSource = null;
SharedPlacesListBox.ItemsSource = sharedPlaces;
SharedPlacesListBox.SelectedIndex = -1;

User can add a new Interesting Place with by clicking ApplicationBarAddIconButton. It will navigate to AddPlacePage page where a new Interesting Place data is asked.

private void ApplicationBarAddIconButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri("/AddPlacePage.xaml", UriKind.Relative));
}

User can view his/her own or shared Interesing Place information by clicking the list items. ShowPlaceDetailsPage page will be described later in this code example.

private void MyPlacesListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MyPlacesListBox.SelectedIndex == -1) return;
int selectedIndex = (sender as ListBox).SelectedIndex;
this.NavigationService.Navigate(new Uri("/ShowPlaceDetailsPage.xaml?selectedIndex=" + selectedIndex + "&list=MyPlaces", UriKind.Relative));
}
private void SharedPlacesListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (SharedPlacesListBox.SelectedIndex == -1) return;
int selectedIndex = (sender as ListBox).SelectedIndex;
this.NavigationService.Navigate(new Uri("/ShowPlaceDetailsPage.xaml?selectedIndex=" + selectedIndex + "&list=SharedPlaces", UriKind.Relative));
}

Adding a new Interesting Place

Here in a add new Interesting Place user can give basic information (title and text) about a new place, take a image and record audio or video. Location will be watched if device supports Location Services.

Add a new Interesting Place

Design (AddPlacePage.xaml)

Page UI uses basic controls like TextBlocks, Buttons, TextBoxs and one Image and one MediaElement to playback video.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Height="30" HorizontalAlignment="Left" Margin="12,24,0,0" Name="textBlock2" Text="Title:" VerticalAlignment="Top" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="14,232,0,0" Name="textBlock1" Text="Image:" VerticalAlignment="Top" />
<Image Height="150" HorizontalAlignment="Left" Margin="80,174,0,0" Name="ImageElement" Stretch="Fill" VerticalAlignment="Top" Width="200" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="60,6,0,0" Name="TitleTextBox" Text="" VerticalAlignment="Top" Width="396" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="14,84,0,0" Name="textBlock3" Text="Text:" VerticalAlignment="Top" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="60,65,0,0" Name="TextTextBox" Text="" VerticalAlignment="Top" Width="396" />
<Button Content="Record" Height="72" HorizontalAlignment="Left" Margin="70,465,0,0" Name="RecordAudioButton" VerticalAlignment="Top" Width="146" Click="RecordAudioButton_Click" />
<Button Content="Play" Height="72" HorizontalAlignment="Right" Margin="0,465,102,0" Name="PlayAudioButton" VerticalAlignment="Top" Width="146" Click="PlayAudioButton_Click" />
<Button Content="Save" Height="72" HorizontalAlignment="Left" Margin="23,534,0,0" Name="SavePlaceButton" VerticalAlignment="Top" Width="210" Click="SavePlaceButton_Click" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="12,377,0,0" Name="textBlock4" Text="Video:" VerticalAlignment="Top" />
<Button Content="Record" Height="72" HorizontalAlignment="Left" Margin="290,332,0,0" Name="RecordVideoButton" VerticalAlignment="Top" Width="160" Click="RecordVideoButton_Click" />
<Button Content="Play" Height="72" HorizontalAlignment="Left" Margin="290,391,0,0" Name="PlayVideoButton" VerticalAlignment="Top" Width="160" Click="PlayVideoButton_Click" />
<Button Content="Take" Height="72" HorizontalAlignment="Left" Margin="290,163,0,0" Name="TakeImageButton" VerticalAlignment="Top" Width="160" Click="TakeImageButton_Click" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="14,484,0,0" Name="textBlock5" Text="Audio:" VerticalAlignment="Top" />
<Button Content="Cancel" Height="72" HorizontalAlignment="Left" Margin="225,534,0,0" Name="CancelButton" VerticalAlignment="Top" Width="210" />
<MediaElement Height="120" HorizontalAlignment="Left" Margin="80,337,0,0" Name="VideoPlayer" VerticalAlignment="Top" Width="200" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="9,133,0,0" Name="textBlock6" Text="Location:" VerticalAlignment="Top" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="108,133,0,0" Name="LocationTextBlock" Text="" VerticalAlignment="Top" />
</Grid>

Programming (AddPlacePage.xaml)

This page has a quite a many member variable used in this page. A few of them is used to create a new Interesting Place (basic data) and others are used with media capturing or determining location of the device.

// Reference to App
private App app = App.Current as App;
// base filename for all files
private string filenameBase;
private DateTime dateTime;
// a new place
private Place place = new Place();
// Image
private CameraCaptureTask cameraTask;
private BitmapImage bitmapImage;
// Audio
private Microphone microphone = Microphone.Default;
private byte[] audioBuffer;
private MemoryStream audioMemoryStream;
private SoundEffect soundEffect;
private DispatcherTimer dispatcherTimer;
// Location
private GeoCoordinateWatcher watcher;

Each of the new Interest Place has unique filenameBase which is based to date and time. InitializeFileName method will be called from the constructor of this Page. Filename will be following format: yyyymmddminsecmsec (jpg|aud|mp4).

private string InitializeFileName()
{
// DateTime object
dateTime = DateTime.Now;
// year
string filename = dateTime.Year.ToString();
// month
if (dateTime.Month.ToString().Count() == 1) filename += "0";
filename += dateTime.Month.ToString();
// day
if (dateTime.Day.ToString().Count() == 1) filename += "0";
filename += dateTime.Day.ToString();
// hour
if (dateTime.Hour.ToString().Count() == 1) filename += "0";
filename += dateTime.Hour.ToString();
// min
if (dateTime.Minute.ToString().Count() == 1) filename += "0";
filename += dateTime.Minute.ToString();
// sec
if (dateTime.Second.ToString().Count() == 1) filename += "0";
filename += dateTime.Second.ToString();
// msec
if (dateTime.Millisecond.ToString().Count() == 1) filename += "0";
filename += dateTime.Millisecond.ToString();
return filename;
}

Device location will be detected when this page is navigated to.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
if (watcher == null)
{
// use high accuracy
watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
// use MovementThreshold to ignore noise in the signal
watcher.MovementThreshold = 20;
// detect position changes
watcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(WatcherPositionChanged);
watcher.Start();
}
}

Latitude and longitude postion will be stoted to place object and will be displayed in LocationTextBlock in the UI.

void WatcherPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
place.Latitude = e.Position.Location.Latitude.ToString();
place.Longitude = e.Position.Location.Longitude.ToString();
LocationTextBlock.Text = "Lat: " + e.Position.Location.Latitude.ToString("0.000") +
"Lng: " + e.Position.Location.Longitude.ToString("0.000");
}

GeoCoordinateWatcher object will be stopped when user navigates from this page.

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
watcher.Stop();
}

Image is captured with CameraCaptureTask class. TakeImageButton_Click method will be called when TakeImageButton is clicked. CameraCaptureTask open phone's default camera capture software and CameraTaskCompleted method will be called when image is taken.

private void TakeImageButton_Click(object sender, RoutedEventArgs e)
{
cameraTask = new CameraCaptureTask();
cameraTask.Completed += new EventHandler<PhotoResult>(CameraTaskCompleted);
cameraTask.Show();
}

CameraTaskCompleted method will create a new BitmapImage to store captured image from PhotoResult. Image will be saved later to the Isolated Storage. Taken image will be shown in the UI with Image control.

private void CameraTaskCompleted(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
bitmapImage = new BitmapImage();
bitmapImage.SetSource(e.ChosenPhoto);
ImageElement.Source = bitmapImage;
}
cameraTask.Completed -= new EventHandler<PhotoResult>(CameraTaskCompleted);
}

Recording audio here in Silverlight based application is a little complicated. XNA framework has to be used and XNA Game Studio game loop has to be simulated, because Microphone is from XNA Game Studio. RecordAudioButton_Click method will be called when RecordAudioButton is clicked. DispatcherTimer class is used to create timer to simulate game loop and call XNA Update method. Audio is captured from the microphone and stored to the MemoryStream object. This same method will stop audio recording, timer and Update method calls when recording is stopped.

Note: You have to add references to Microsoft.Xna.Framework to capture audio.

private void RecordAudioButton_Click(object sender, RoutedEventArgs e)
{
if ((string)RecordAudioButton.Content == "Record")
{
// timer to simulate the XNA Game Studio game loop (Microphone is from XNA Game Studio)
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(50);
dispatcherTimer.Tick += delegate { try { FrameworkDispatcher.Update(); } catch { } };
dispatcherTimer.Start();
// captured audio buffer is ready
microphone.BufferReady += new EventHandler<EventArgs>(MicrophoneBufferReady);
// start recording
audioMemoryStream = new MemoryStream();
microphone.BufferDuration = TimeSpan.FromMilliseconds(1000);
audioBuffer = new byte[microphone.GetSampleSizeInBytes(microphone.BufferDuration)];
microphone.Start();
RecordAudioButton.Content = "Stop";
}
else if ((string)RecordAudioButton.Content == "Stop" && microphone.State == MicrophoneState.Started)
{
RecordAudioButton.Content = "Record";
microphone.Stop();
dispatcherTimer.Stop();
dispatcherTimer.Tick -= delegate { try { FrameworkDispatcher.Update(); } catch { } };
microphone.BufferReady -= new EventHandler<EventArgs>(MicrophoneBufferReady);
}
}

Recorded audio from microphone will be saved to memory stream in every one second.

public void MicrophoneBufferReady(object sender, EventArgs args)
{
microphone.GetData(audioBuffer);
audioMemoryStream.Write(audioBuffer, 0, audioBuffer.Length);
}

Recorded audio will be played when PlayAudioButton is clicked.

private void PlayAudioButton_Click(object sender, RoutedEventArgs e)
{
soundEffect = new SoundEffect(audioMemoryStream.ToArray(), microphone.SampleRate, AudioChannels.Mono);
soundEffect.Play();
}

RecordVideoButton calls RecordVideoButton_Click method when button is clicked in the screen. Video recording is handled with own new page (RecordVideoPage). RecordVideoPage.xaml will be described later in this coding example. Filename will be send to Page with Uri string.

private void RecordVideoButton_Click(object sender, RoutedEventArgs e)
{
this.NavigationService.Navigate(new Uri("/RecordVideoPage.xaml?filename=" + filenameBase + ".mp4", UriKind.Relative));
}

Recording Video Page will store video to the Isolated Storage and video can be played here in add a new Interesting Place from the Isolated Storage using IsolatedStorageFileStream class. PlayVideoButton_Click method will be called when PlayVideoButton is clicked. Video will be played using with MediaElement (named to VideoPlayer).

private void PlayVideoButton_Click(object sender, RoutedEventArgs e)
{
// Create the file stream and attach it to the MediaElement.
IsolatedStorageFileStream isoVideoFile = new IsolatedStorageFileStream(
filenameBase+".mp4",
FileMode.Open, FileAccess.Read,
IsolatedStorageFile.GetUserStoreForApplication());
VideoPlayer.SetSource(isoVideoFile);
VideoPlayer.Play();
}

Captured image and audio files will be saved to the Isolated Storage here in this page when Save Button is clicked. First connection to user Isolated Storage is opened and then WriteableBitmap object is used to store captured BitmapImage and finally image data is saved to the Isolated Storage. Same way audio memory stream is written to the Isolated Storage. Finally place object is filled with this new Interesting Place data, a new object is added to MyPlaces list in App class and navigeted back to Main Page.

private void SavePlaceButton_Click(object sender, RoutedEventArgs e)
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
 
// save image to Isolated Storage
if (bitmapImage != null)
{
// delete if already exists
if (isoStore.FileExists(filenameBase + ".jpg")) isoStore.DeleteFile(filenameBase + ".jpg");
// create file stream to Isolated Storage
IsolatedStorageFileStream fileStream = isoStore.CreateFile(filenameBase + ".jpg");
// create WriteableBitmap object from captured BitmapImage
WriteableBitmap writeableBitmap = new WriteableBitmap(bitmapImage);
place.ImageWidth = writeableBitmap.PixelWidth;
place.ImageHeight = writeableBitmap.PixelHeight;
// save to Isolated Storage
writeableBitmap.SaveJpeg(fileStream, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight, 0, 100);
fileStream.Close();
place.Image = filenameBase + ".jpg";
}
 
// save audio to Isolated Storage
if (audioMemoryStream != null)
{
if (isoStore.FileExists(filenameBase + ".aud")) isoStore.DeleteFile(filenameBase + ".aud");
using (IsolatedStorageFileStream audFileStream = isoStore.OpenFile(filenameBase + ".aud", FileMode.CreateNew))
{
audioMemoryStream.WriteTo(audFileStream);
}
place.Audio = filenameBase + ".aud";
}
 
// video is already saved to Isolated Storage
if (isoStore.FileExists(filenameBase + ".mp4"))
{
place.Video = filenameBase + ".mp4";
}
 
// title and text
place.Title = TitleTextBox.Text;
place.Text = TextTextBox.Text;
 
// add data
place.Date = dateTime.ToShortDateString();
 
// add place to MyPlaces
app.MyPlaces.Add(place);
app.IsPlaceAdded = true;
 
// go back to Main page
if (NavigationService.CanGoBack)
{
NavigationService.GoBack();
}
}

Recording Video

Design (RecordVideoPage.xaml)

There is a one excellent tutorial to record and save video to Isolated Storage in MSDN.

IC531100.png

Look, design and create a new RecordVideoPage.xaml using this tutorial from MSDN - How to: Record Video in a Camera Application for Windows Phone.

Programming (RecordVideoPage.xaml)

Use above link and do programming as described there. That example use hard coded CameraMovie.mp4 filename to store recorded video to the Isolated Storage. In this example filename is send from the Main Page to this RecordVideoPage page.

Modify OnNavigatedTo method to get filename from Main Page.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
 
// get filename from previous page
IDictionary<string, string> parameters = this.NavigationContext.QueryString;
if (parameters.ContainsKey("filename"))
{
isoVideoFileName = parameters["filename"];
}
 
// Initialize the video recorder.
InitializeVideoRecorder();
}

Now video will be saved using a new Interesting Place object name for a video file.

Show the Interesting Place

User own and shared Interesting Places from server is showed with this Page.

Interesting Place

Design (ShowPlaceDetailsPage.xaml)

Design of this page is simple with a few controls: Image, TextBlock and Buttons. TextWrapping is used in TextBlock to get more than one row of text displayed in TextBlock.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Image Height="330" HorizontalAlignment="Left" Margin="7,6,0,0" Name="PlaceImage" Stretch="Fill" VerticalAlignment="Top" Width="440" />
<TextBlock Height="100" TextWrapping="Wrap" HorizontalAlignment="Left" Margin="7,342,0,0" Name="PlaceTextBlock" Text="Sample text here, and even more and even more and even more..." VerticalAlignment="Top" Width="440" FontStyle="Italic" />
<Button Content="Video" Height="72" HorizontalAlignment="Left" Margin="0,448,0,0" Name="VideoButton" VerticalAlignment="Top" Width="160" Click="VideoButton_Click" />
<Button Content="Audio" Height="72" HorizontalAlignment="Left" Margin="0,515,0,0" Name="AudioButton" VerticalAlignment="Top" Width="160" Click="AudioButton_Click" />
<Button Content="Map" Height="72" HorizontalAlignment="Left" Margin="287,448,0,0" Name="MapButton" VerticalAlignment="Top" Width="160" Click="MapButton_Click" />
<Button Content="Share" Height="72" HorizontalAlignment="Left" Margin="287,515,0,0" Name="ShareButton" VerticalAlignment="Top" Width="160" Click="ShareButton_Click" />
</Grid>

Programming (ShowPlaceDetailsPage.xaml)

Same kind of variables are used here like it was in adding a new Interesting Place, because same media files are used in playback here too. Only a new Popup class object is used which opens a new UserControl when data and media are sent to server. MediaPlayerLaucher class is used to show video to the end user.

// reference to App Class
private App app = App.Current as App;
// selected place index
private int selectedIndex;
// play sound
private Microphone microphone = Microphone.Default;
private DispatcherTimer dispatcherTimer;
private SoundEffect soundEffect;
// popup
private Popup uploadPopup;
// play video from net - shared
private MediaPlayerLauncher VideoLaucher;

OnNavigatedTo method will take care about the parameters from the Main Page - which list is selected (My Places or Shared Places) and what was the selected index from the list. XNA game loop simulation is also started to play SoundEffect.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
IDictionary<string, string> parameters = this.NavigationContext.QueryString;
// page is navigated from Main Page and list item is selected rom MyPlaces list
if (parameters.ContainsKey("list") && parameters.ContainsKey("selectedIndex"))
{
selectedIndex = Int32.Parse(parameters["selectedIndex"]);
app.SelectedList = parameters["list"];
}
// timer to simulate the XNA Game Studio game loop
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(50);
dispatcherTimer.Tick += delegate { try { FrameworkDispatcher.Update(); } catch { } };
dispatcherTimer.Start();
// show data in screen
ShowData();
}

OnNavigatedFrom method will stop the timer to call XNA Update method.

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
if (dispatcherTimer != null)
{
dispatcherTimer.Stop();
dispatcherTimer.Tick -= delegate { try { FrameworkDispatcher.Update(); } catch { } };
}
}

Above OnNavigatedTo method calls ShowsData method to display selected Interesting Places data to the screen. Image data will be loaded from the Isolated Storage if this page is navigated from Main Page's My Places list, other wise Shared List data will be used.

public void ShowData()
{
if (app.SelectedList == "MyPlaces")
{
// page title
PageTitle.Text = app.MyPlaces[selectedIndex].Title;
// read image data from Isolated Storage
if (app.MyPlaces[selectedIndex].Image != null)
{
BitmapImage imageFromStorage = new BitmapImage();
using (var isoFile = IsolatedStorageFile.GetUserStoreForApplication())
{
using (var imageStream = isoFile.OpenFile(app.MyPlaces[selectedIndex].Image, FileMode.Open, FileAccess.Read))
{
imageFromStorage.SetSource(imageStream);
}
}
// show image in screen
PlaceImage.Source = imageFromStorage;
}
// text
PlaceTextBlock.Text = app.MyPlaces[selectedIndex].Text;
// video
if (app.MyPlaces[selectedIndex].Video == null) VideoButton.IsEnabled = false;
// audio
if (app.MyPlaces[selectedIndex].Audio == null) AudioButton.IsEnabled = false;
// shared
if (app.MyPlaces[selectedIndex].IsShared || app.nickname == "") ShareButton.IsEnabled = false;
}
else
{
// page title
PageTitle.Text = app.SharedPlaces[selectedIndex].Title;
if (app.SharedPlaces[selectedIndex].Image != "")
{
Uri uri = new Uri("http://YOUR SERVER IP HERE/InterestingPlaces/files/" + app.SharedPlaces[selectedIndex].Image, UriKind.Absolute);
// create a new BitmapImage from Uri and display in screen
PlaceImage.Source = new BitmapImage(uri);
}
// text
PlaceTextBlock.Text = app.SharedPlaces[selectedIndex].Text;
// video
if (app.SharedPlaces[selectedIndex].Video == "") VideoButton.IsEnabled = false;
// audio
if (app.SharedPlaces[selectedIndex].Audio == "") AudioButton.IsEnabled = false;
// net file cannot be shared
ShareButton.IsEnabled = false;
}
}

This code example uses two different ways to demonstrate video playback.

Video file will be played using PlayVideoPage page if Interesting Place is selected from the My Places list. In this situation, video file is loaded from the Isolated Storage and it is easy to use same kind of mechanism what is used in recording video file earlier in this code example. PlayVideoPage page is like a RecordVideoPage page but all the recording capabilities are removed (look more information about PlayVideoPage.xaml and .cs from the source files of this coding example).

Video file will be played using MediaPlayerLauncher class when VideoButton is clicked and Shared Places list is used to get in this page. MediaPlayerLaucher object opens Windows Phone default media player and start downloading a video file from the server.

private void VideoButton_Click(object sender, RoutedEventArgs e)
{
if (app.SelectedList == "MyPlaces")
{
this.NavigationService.Navigate(new Uri("/PlayVideoPage.xaml?filename=" + app.MyPlaces[selectedIndex].Video, UriKind.Relative));
}
else
{
VideoLaucher = new MediaPlayerLauncher();
VideoLaucher.Controls = MediaPlaybackControls.All;
VideoLaucher.Location = MediaLocationType.Install;
Uri uri = new Uri("http://YOUR SERVER IP HERE/InterestingPlaces/files/" + app.SharedPlaces[selectedIndex].Video, UriKind.Absolute);
VideoLaucher.Media = uri;
VideoLaucher.Show();
}
}

Same kind of selection is made with Audio playback (local or server). AudioButton_Click method will be called when AudioButton is clicked. IsolatedStorageFileStream class is used to load sound bytes from the Isolated Storage and sound is played with SoundEffect class.

private void AudioButton_Click(object sender, RoutedEventArgs e)
{
// stop playing audio
if ((string)AudioButton.Content == "Stop")
{
AudioButton.Content = "Audio";
soundEffect.Dispose();
return;
}
// start playing audio
AudioButton.Content = "Stop";
// from Isolated Storage
if (app.SelectedList == "MyPlaces")
{
using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication())
{
if (isoStore.FileExists(app.MyPlaces[selectedIndex].Audio))
{
// read file from Isolated Storage
IsolatedStorageFileStream fileStream = isoStore.OpenFile(app.MyPlaces[selectedIndex].Audio, FileMode.Open, FileAccess.Read);
Byte[] bytes = new byte[fileStream.Length];
fileStream.Read(bytes, 0, bytes.Length);
// play recorded audio
soundEffect = new SoundEffect(bytes.ToArray(), microphone.SampleRate, AudioChannels.Mono);
soundEffect.Play();
}
}
}
else // from server
{
Uri uri = new Uri("http://USER YOUR OWN IP HERE/InterestingPlaces/files/" + app.SharedPlaces[selectedIndex].Audio, UriKind.Absolute);
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri);
request.AllowReadStreamBuffering = false;
request.BeginGetResponse(new AsyncCallback(GetAudioData), request);
}
}

Loading sound bytes from the server side is more complicated. A new request to server side is made with HttpWebRequest class, which uses GetAudioData method to read and store bytes to MemoryStream object.

private void GetAudioData(IAsyncResult result)
{
HttpWebRequest request = (HttpWebRequest) result.AsyncState;
HttpWebResponse response = (HttpWebResponse) request.EndGetResponse(result);
Stream stream = response.GetResponseStream();
byte[] bytes = new byte[50*1024];
int read;
// stream to store audio bytes
MemoryStream memoryStream = new MemoryStream();
// read bytes
while ((read = stream.Read(bytes, 0, bytes.Length)) > 0)
{
memoryStream.Write(bytes, 0, read);
}
// play recorded audio
soundEffect = new SoundEffect(memoryStream.ToArray(), microphone.SampleRate, AudioChannels.Mono);
soundEffect.Play();
}

Users own Interesting Places can be shared to server side. ShareButton_Click method will be called when ShareButton is clicked. This will create a new Popup UploadPopup UserControl. Uploading data and media files to server side is described later in this article.

private void ShareButton_Click(object sender, RoutedEventArgs e)
{
// show Popup
uploadPopup = new Popup();
uploadPopup.Child = new UploadPopup(app.MyPlaces[selectedIndex]);
uploadPopup.IsOpen = true;
}

This UploadPopup will be removed from the screen when user data is uploaded to server and user is navigating back.

protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
if (uploadPopup != null && uploadPopup.IsOpen)
{
// hide the popup
uploadPopup.IsOpen = false;
// cancel subsequent BackKey navigation
e.Cancel = true;
// Interesting Places is now shared
app.MyPlaces[selectedIndex].IsShared = true;
ShareButton.IsEnabled = false;
}
base.OnBackKeyPress(e);
}

Location of the Interesting Place is displayed with ShowMapPage page. This page is navigated to when the user clicks MapButton in the screen. ShowMapPage page is described lated in this example. Selected list and selectedIndex is passed to ShowMapPage page to display right data from the App class.

private void MapButton_Click(object sender, RoutedEventArgs e)
{
if (app.SelectedList == "MyPlaces")
{
this.NavigationService.Navigate(new Uri("/ShowMapPage.xaml?selectedIndex=" + selectedIndex + "&list=MyPlaces", UriKind.Relative));
}
else
{
this.NavigationService.Navigate(new Uri("/ShowMapPage.xaml?selectedIndex=" + selectedIndex + "&list=SharedPlaces", UriKind.Relative));
}
}

Uploading data and media files to the server

Uploading process is shown in the Popup UserContol.

Uploading a new Interesting Place to server

Design (UploadPopup.xaml)

A new UserControl can be added to the project in same way as to create a new page. Select project from Solution Explorer, select add, and New Item... Select UserControl and name it to UploadPopup.

Create a new UserControl

This UserControl is only a semitransparent (opacity 90%) Grid with one TextBlock to show uploading information to user.

<Grid x:Name="LayoutRoot" Width="480" Height="800" Background="Black" Opacity="0.9">
<TextBlock Height="300" HorizontalAlignment="Center"
Name="UploadingTextBlock" Text="Uploading:" VerticalAlignment="Center" Width="317" Margin="82,254,82,243" />
</Grid>

Programming (UploadPopup.xaml)

Sending data and media files is done with RestSharp class. The main idea is to send small chunks to server side to keep Windows Phone application in life. There are a few member variables described to handle sending process.

// Interesting Place to send server
private Place place;
// bytes to send server
private byte[] bytes;
// which media to send
private List<string> medias = new List<string>();
// max chunk size
private const int MAXCHUNKSIZE = 500000;
// how many sends are done
private int send = 0;
// how many send max
private int sends = 0;
// filename to send
private string filename;

The new Interesting Place object is passed to this class via constructor. There are medias List, which holds all the sending media filenames to send.

public UploadPopup(Place place)
{
InitializeComponent();
 
// place to member variables
this.place = place;
// which media to send
if (place.Image != null) medias.Add(place.Image);
if (place.Audio != null) medias.Add(place.Audio);
if (place.Video != null) medias.Add(place.Video);
// start uploading data to server
StartUploadData();
}

StartUploadData method will be called after constructor and Interesting Places data will be sent to server first. Interesting Place title, text, date, filenames and location information will be send via POST method to server. If all data is sent to server side successfully then media files will be sent with SendFileToServer method.

private void StartUploadData()
{
// send data to server
UploadingTextBlock.Text += "\n- data";
// create RestRequest object
RestRequest request = new RestRequest("http://YOUR SERVER IP HERE/InterestingPlaces/addData.php", Method.POST);
request.AddParameter("title", place.Title);
request.AddParameter("text", place.Text);
request.AddParameter("date", place.Date);
if (place.Image != null) request.AddParameter("image", place.Image);
else request.AddParameter("image", "");
if (place.Audio != null) request.AddParameter("audio", place.Audio);
else request.AddParameter("audio", "");
if (place.Video != null) request.AddParameter("video", place.Video);
else request.AddParameter("video", "");
request.AddParameter("latitude", place.Latitude);
request.AddParameter("longitude", place.Longitude);
request.AddParameter("nickname", "pasi");
// create RestClient object to send data to server
RestClient restClient = new RestClient();
// exevute request and get response from server side
restClient.ExecuteAsync(request, (response) =>
{
if (response.StatusCode == HttpStatusCode.OK)
{
// upload successfull
UploadingTextBlock.Text += "(done)";
// upload image
SendFileToServer();
}
else
{
// error ocured during upload
UploadingTextBlock.Text += "(error)";
}
});
}

In server sid PHP file (addData.php) will be called. This PHP file will insert a new row to the MySQL table.

// Created by Juha Peltomäki
<?php
// user information
$dbHost = "localhost";
$dbUser = "username";
$dbPass = "password";
$dbName = "database";
 
// connect database
$mysqli = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
if (mysqli_connect_errno()) { echo mysqli_connect_error(); }
 
// insert new values to table
$insert_sql = "INSERT INTO InterestingPlaces VALUES (?,?,?,?,?,?,?,?,?,?)";
if ($stmt = $mysqli->prepare($insert_sql)) {
$id = mysqli_insert_id($mysqli);
$stmt->bind_param('isssssssss',$id,$_POST['title'],$_POST['text'],$_POST['date'],$_POST['image'],$_POST['audio'],$_POST['video'],$_POST['latitude'],$_POST['longitude'],$_POST['nickname']);
$stmt->execute();
 
$stmt->close();
echo "Data inserted to database.\n";
} else {
echo $mysqli->error;
echo "Error inserting data.\n";
}
$mysqli->close();
?>

SendFileToServer method will read media bytes from the Isolated Storage and it calls StartSendingMedia method to start sending process.

private void SendFileToServer()
{
// there are still media files to send server
if (medias.Count > 0)
{
filename = medias.First();
medias.RemoveAt(0);
}
else
{
UploadingTextBlock.Text += "\nAll done!";
return;
}
// uploading file to server
UploadingTextBlock.Text += "\n- "+filename;
// read bytes from isolated storage
using (IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication())
{
IsolatedStorageFileStream isolatedStorageFileStream = isolatedStorageFile.OpenFile(filename, FileMode.Open, FileAccess.Read);
bytes = new byte[isolatedStorageFileStream.Length];
isolatedStorageFileStream.Read(bytes, 0, bytes.Length);
isolatedStorageFileStream.Dispose();
}
// start sending media
StartSendingMedia();
}

StartSendingMedia method calculates how many sends there will be and call SendChunk method to actually start sending process.

private void StartSendingMedia()
{
sends = bytes.Length / MAXCHUNKSIZE + 1;
send = 0;
SendChunk();
}

SendChunk uses recursion to send media file chunks to server side. In this coding example chunk size will be 500 kB or less if last chunk is in sending, then also last parameter value will be True (so server side knows to parse chunks together). If all the chunks of the media file is send, then next media file will be in sending process.

private void SendChunk()
{
UploadingTextBlock.Text += ".";
// calculate sending chunk size
int count = MAXCHUNKSIZE;
bool last = false;
// if last, send last bytes
if (send == sends - 1)
{
count = bytes.Length - send * MAXCHUNKSIZE;
last = true;
}
// copy sending bytes to sendChunk
byte[] sendChuck = new byte[count];
int srcOffset = send * MAXCHUNKSIZE;
Buffer.BlockCopy(bytes, srcOffset, sendChuck, 0, count);
// create a new request to server
RestRequest request = new RestRequest("http://YOUR SERVER IP HERE/InterestingPlaces/addFile.php", Method.POST);
request.AddParameter("last", last);
request.AddParameter("parts", sends);
request.AddParameter("filename", filename);
request.AddFile("file", sendChuck, filename + ".part_" + send);
// send and wait for responce
RestClient restClient = new RestClient();
restClient.ExecuteAsync(request, (response) =>
{
if (response.StatusCode == HttpStatusCode.OK)
{
//upload successfull
send++;
// send more chunks if not last send yet
if (send < sends) SendChunk();
else
{
UploadingTextBlock.Text += "(done)";
// start sending next file to server
SendFileToServer();
}
}
else
{
//error occured during upload
UploadingTextBlock.Text += "\nError occured during upload!";
}
});
}

In server side PHP file (addFile.php) will take care of all the sended chunks. Sended file parts will be put together with system cat command and file parts will be removed.

<?php
// absolute path to files folder in server
$content_dir = '/home/YOUR OWN PATH HERE/InterestingPlaces/files/';
// if file is uploaded
if (is_uploaded_file($_FILES['file']['tmp_name'])) {
// store file name
$uploadfile = $content_dir . basename($_FILES['file']['name']);
// move file from temp to files folder
if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) {
// if last file -> parse file parts together
if ($_POST['last'] == "True") {
$parts = $_POST['parts'];
$filename = $_POST['filename'];
$command = "cat ";
for ($i=0;$i<$parts;$i++) {
$command .= "files/".$filename.".part_".$i." ";
}
$command .= "> files/".$filename;
exec($command);
// remove file parts
exec("rm -f files/".$filename.".part_*");
echo "File was saved to server";
}
} else {
echo "Error saving file to server.";
}
}
 
?>

Displaying own and Interesting Place location in map

User can see his/her own and Interesting Place location in show map page (ShowMapPage). To use Map control in own Windows Phone applications, user has to create a Bing Maps account (to get Bing Maps key).

Interesting Place in map

Using Map control and get Bing Maps Key

MSDN has a lot of good resources to use maps in WP7 applications:


Follow the first link and create your own Bing Maps account and get the maps key.

Design (ShowMapsPage.xaml)

There is only Map control used in this Page.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<my:Map Height="601" HorizontalAlignment="Left" Margin="9,0,0,0" Name="PlaceMap" VerticalAlignment="Top" Width="441" />
</Grid>

Programming (ShowMapsPage.xaml)

There is two Pushpin objects used in the Map page, one shows the user current location and another shows Interesting Place location. GeoCoordinateWatcher class is used to detect devices location.

// reference to App Class
private App app = App.Current as App;
// selected index of place
private int selectedIndex;
// device location PushPin
private Pushpin devicePin;
private Pushpin placePin;
// device gps watcher
private GeoCoordinateWatcher watcher;

OnNavigatedTo method will get selected list and it's index via parameters. Get location and show places methods will be called.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
IDictionary<string, string> parameters = this.NavigationContext.QueryString;
// page is navigated from Main Page and list item is selected rom MyPlaces list
if (parameters.ContainsKey("list") && parameters.ContainsKey("selectedIndex"))
{
selectedIndex = Int32.Parse(parameters["selectedIndex"]);
app.SelectedList = parameters["list"];
GetLocation();
ShowDeviceInMap();
ShowPlaceInMap();
}
}

OnNavigatedFrom stops the GeoCoordinateWatcher object to check device location.

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
watcher.Stop();
}

Device location is captured like it is done in add a Interesting Place section in this code example. Only exception is that here the device location is passed to devicePin object to show location in Map control.

private void GetLocation()
{
if (watcher == null)
{
// using high accuracy
watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
// use MovementThreshold to ignore noise in the signal
watcher.MovementThreshold = 20;
// position watcher method
watcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(WatcherPositionChanged);
watcher.Start();
}
}
void WatcherPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
// set the location of devicePin object in map
devicePin.Location = e.Position.Location;
// set view to show location of device and Interesting Place location (zoom)
PlaceMap.SetView(LocationRect.CreateLocationRect(placePin.Location, devicePin.Location));
}

OnNavigatedTo method calls ShowDeviceInMap to create a new Pushpin object which is added to the Map control.

private void ShowDeviceInMap()
{
devicePin = new Pushpin();
devicePin.Content = "My Location";
PlaceMap.Children.Add(devicePin);
}

OnNavigatedTo also calls ShowPlaceInMap method to show Interesting Place in Map control. First latitude and longitude values are read from selected list object and GeoCoordinate class is used to center map to pins. Bing Map key has to be set with Map control using with CredentialsProvider.

private void ShowPlaceInMap()
{
double latitude;
double longitude;
string title;
 
// detect lat and lng (Interesting Place)
if (app.SelectedList == "MyPlaces")
{
title = app.MyPlaces[selectedIndex].Title;
latitude = Double.Parse(app.MyPlaces[selectedIndex].Latitude);
longitude = Double.Parse(app.MyPlaces[selectedIndex].Longitude);
}
else
{
title = app.SharedPlaces[selectedIndex].Title;
latitude = Double.Parse(app.SharedPlaces[selectedIndex].Latitude);
longitude = Double.Parse(app.SharedPlaces[selectedIndex].Longitude);
}
PageTitle.Text = title;
// map center
GeoCoordinate mapCenter = new GeoCoordinate(latitude, longitude);
// your map key here
PlaceMap.CredentialsProvider = new ApplicationIdCredentialsProvider("USER YOUR OWN KEY HERE");
// create a new pushpin object
placePin = new Pushpin();
placePin.Location = mapCenter;
placePin.Content = title;
// add pushpin to map control
PlaceMap.Children.Add(placePin);
}

Summary

This code example show how to capture and store media to Windows Phone devide and server side. Hope you find this article useful and it helps you work with media in WP7.

You can download source codes from here: File:PTM InterestingPlaces.zip.

This page was last modified on 26 June 2013, at 13:30.
249 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×