Windows Phone

Windows Phone-Videoaufzeichnung: Ein optimaler Ansatz

Chris Barker

Windows Phone SDK 7.1 eröffnete eine Reihe neuer Entwicklungsszenarien, einschließlich der Möglichkeit, auf die Kamera von Windows Phone 7.5-Geräten zuzugreifen. Mit Windows Phone 8 kamen neue Funktionen hinzu, darunter die Möglichkeit, auf unterstützter Hardware hochauflösende 1080p-Videos aufzunehmen. Während Windows Phone SDK 7.1 nicht erweitert werden konnte, um die neuen Gerätefeatures zu unterstützen, wurde mit Windows Phone 8 auch eine Änderung der Betriebssystemarchitektur eingeführt – es teilt einen Kernel mit Windows 8. Durch eine weitere wesentliche Änderung wurde eine Anzahl APIs in das Windows 8-Modell übernommen. Zudem wurde eine Entwickleroberfläche mit dem Namen Windows-Runtime (WinRT) eingeführt.

Windows Phone SDK 7.1 ist die Version, mit der Entwickler die Features von Windows Phone 7.5 nutzen können. Wenn im Folgenden von Windows Phone 7.5 die Rede ist, sind sowohl die Betriebssystemversion als auch das SDK gemeint. (Der Codename für Windows Phone 7.5 war übrigens „Mango“ und der für Windows Phone 8 „Apollo“ – Sie werden diese Namen möglicherweise in anderen Texten finden.)

Mit der Windows-Runtime wurde nicht nur eine Anzahl konsistenter APIs für Windows Phone und Windows 8 eingeführt, sondern darüber hinaus auch eine viel effizientere systemeigene Runtime, durch die neue Windows Phone-spezifische APIs das gleiche Modell verwenden können. Ich werde nicht im Einzelnen auf die neuen Änderungen eingehen, die mit der Windows-Runtime eingeführt wurden, aber denken Sie daran, dass sie Auswirkungen auf APIs haben, beispielsweise auf die zur Videoaufzeichnung.

Ich werde Ihnen in einem Überblick die Unterschiede zwischen den Versionen aufzeigen, aber hauptsächlich werden Sie erfahren, wie Sie Ihr Windows Phone 7.5-Projekt weiterführen können, während Sie gleichzeitig die Erfahrungen Ihrer Windows Phone 8-Benutzer verbessern. Ein großer Vorteil dabei ist, dass die vorgestellten Techniken nicht nur auf die Videoaufzeichnung anwendbar sind, sondern auf alle APIs, die für Windows Phone 8 neu entwickelt wurden. Ich werde Ihnen anhand eines veröffentlichten Codebeispiels die Techniken erläutern, die Sie zur Wiederverwendung von Code in Ihren Windows Phone 7.5-Projekten, Windows Phone 8-Projekten und sogar in Windows 8-Projekten anwenden können.

Erste Schritte

Ich möchte Ihnen vor allem erläutern, wie Sie eine Lösung nach Windows Phone 8 portieren können, ohne dabei Windows Phone 7.5-Geräte zu vernachlässigen. Bevor ich darauf eingehe, lohnt es sich, einen Schritt zurückzugehen und festzustellen, dass Sie in vielen Fällen überhaupt nichts unternehmen müssen. Ihre vorhandene Windows Phone 7.5-App wird auch unter Windows Phone 8 funktionieren. Sie sollten sie jedoch testen, um sicherzustellen, dass sie sich wie erwartet verhält. Visual Studio 2012 lässt Sie nur die in Windows Phone 7.5 unterstützten APIs verwenden. Und denken Sie daran, Sie müssen nur dann Code für Windows Phone 8 schreiben, wenn Sie die Vorteile einer der neuen APIs und deren Funktionalität nutzen möchten.

Ich gehe davon aus, dass Sie Ihre Windows Phone 7.5-Benutzer auch weiterhin unterstützen möchten, während Sie Ihren Windows Phone 8-Benutzern den Zugriff auf die umfangreichen neuen Features ihrer Geräte ermöglichen.

Um zu erklären, wie eine optimale Lösung aussehen kann, werde ich das Videorekorderbeispiel für Windows Phone 7.5 (bit.ly/16tM2c1) als Ausgangspunkt verwenden. Obwohl ich hier den Code des Videorekorderbeispiels verwende, können Sie, wie bereits erwähnt, die hier vorgestellten Ansätze auf beliebige Plattformfeatures anwenden, die Sie auf unterschiedlichen Plattformversionen bereitstellen möchten.

Einige Anmerkungen zu dieser Lösung:

  • Das MVVM-Entwurfsmuster (Model-View-ViewModel) wird nicht verwendet.
    • Der Hauptteil der Benutzeroberfläche liegt in der Datei „MainPage.xaml“.
    • Der Hauptteil der Logik wird in der Codebehind-Datei „MainPage.xaml.cs“ ausgeführt.
  • Zur Manipulation der Kamera wird die Windows Phone 7.5-API verwendet, insbesondere die Klasse „System.Windows.Media.VideoCaptureDevice“. Mit Windows Phone 8 wurde eine neue API für diesen Anwendungsbereich eingeführt, auf die ich später eingehe.

Die Tatsache, dass diese Lösung das MVVM-Entwurfsmuster nicht verwendet, ist kaum von Bedeutung. Das MVVM-Entwurfsmuster ist zwar in der Regel für Produktionscode empfehlenswert, da es Ihnen später einige Umgestaltungsarbeit erspart, stellt aber an sich kein Kompatibilitätskriterium dar.

Dagegen führt die VideoCaptureDevice-Klasse bei der Umstellung auf Windows Phone 8 zu Einschränkungen. Sie wird zwar von der Windows Phone 8-Runtime ausgeführt, aber nicht mit maximaler Leistung, und sie gestattet (unter anderem) nicht die Nutzung aller durch die Hardware unterstützten Auflösungen.

In den folgenden Abschnitten werde ich von folgendem Ansatz ausgehen:

  • Erstellt wird eine allgemeine Abstraktion der Funktionalität, die auf verschiedenen Plattformen unterschiedlich implementiert wird. Dadurch können Sie den Code für diese Funktionalität plattformübergreifend verwenden.
  • Die Abstraktion für einen Videorekorder wird in eine portable Klassenbibliothek (Portable Class Library, PCL) übertragen. Dadurch ist es einfacher, später bei Bedarf weiteren Code in die PCL aufzunehmen.
  • Der MainPage-Codebehind wird über Dateiverknüpfungen freigegeben (eine pragmatische Entscheidung, weil die bestehende App das MVVM-Entwurfsmuster nicht verwendet – andernfalls würde ich empfehlen, die Ansichtsmodelle in eine PCL zu übertragen).

Für einen optimalen Ansatz ist nun zu überlegen, wie die Windows Phone 7.5-Kameralogik aus der Datei „MainPage.xaml.cs“ abstrahiert werden kann. Ich werde versuchen, dieses Problem im nächsten Abschnitt zu lösen.

Entkopplung und Abstraktion für Ihre Implementierung

Zuerst müssen Sie sich entscheiden, welche Bibliotheksart Sie für Ihren Code verwenden möchten. Eine Klassenbibliothek wäre sinnvoll, aber Sie sollten eine PCL verwenden. Mit einer PCL können Sie Zielplattformen angeben und sich auf einfache Weise auf die APIs beschränken, die allen Plattformen gemeinsam sind – wodurch Sie die Möglichkeit erhalten, projektübergreifende binäre Verweise zu verwenden (statt einfacher Code-/Projektverknüpfung und Wiederherstellung auf der Zielplattform zur Kompilierungszeit). Im vorliegenden Fall erstellen Sie ein PCL-Projekt sowohl für Windows Phone 7.5 (oder höher) als auch für Windows Store-Apps (also für „moderne“ Windows 8-Apps) und nennen es „VideoRecorder.Shared“ (siehe Abbildung 1).

Configuring Your Portable Class LibraryAbbildung 1 – Konfigurieren Ihrer portablen Klassenbibliothek

Entsprechend dem Abstraktionsmuster (bit.ly/YQwsVD) können Sie eine abstrakte Klasse „VideoRecorderCore“ erstellen, mit der bei Bedarf zielplattformspezifischer Code zur Verfügung steht. Die abstrakte Klasse hat die in Abbildung 2 dargestellte Form.

Abbildung 2 – Erstellen der abstrakten Klasse „VideoRecorderCore“

namespace VideoRecorder.Shared
{
  public abstract class VideoRecorderCore
  {
    public abstract void InitializeVideoRecorder();
    public abstract void StartVideoRecording();
    public abstract void StopVideoRecording();
    public abstract void StartVideoPreview();
    public abstract void DisposeVideoPlayer();
    public abstract void DisposeVideoRecorder();
    public static VideoRecorderCore Instance { get; set; }
  }
}

Anmerkung: In dem Beispiel in Abbildung 2 könnten Sie genauso gut eine Schnittstelle verwenden, aber wahrscheinlich werden Sie in vielen Szenarien eine gemeinsame Basisfunktionalität benötigen.

Im sdkVideoRecorderCS-Projekt würden Sie wohl einige Zeit mit der Implementierung des MVVM-Entwurfsmusters verbringen, aber das kann auf einen anderen Tag verschoben werden. Bei der Umgestaltung sollten Sie sich jetzt nur auf die Bereitstellung einer konkreten Implementierung der abstrakten Klasse konzentrieren. Glücklicherweise verfügen Sie bereits über eine konkrete Implementierung, sie ist allerdings ein wenig zu stark in „MainPage.xaml.cs“ verankert. Um dies zu beheben, können Sie eine WP7VideoRecorder-Klasse im sdkVideoRecorderCS-Projekt erstellen und sie im PCL-Projekt von „VideoRecorderCore“ erben. Als nächstes müssen Sie nur die Implementierung aus „MainPage.xaml.cs“ in die entsprechende überschriebene Methode überführen. Beispielsweise würde die InitializeVideoRecorder-Methode zunächst aussehen wie in Abbildung 3 dargestellt.

Abbildung 3 – Die InitializeVideoRecorder-Methode

public override void InitializeVideoRecorder()
{
  if (captureSource == null)
  {
    captureSource = new CaptureSource();
    fileSink = new FileSink();
    videoCaptureDevice =
       CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
    captureSource.CaptureFailed +=
    new EventHandler<ExceptionRoutedEventArgs>(OnCaptureFailed);
    if (videoCaptureDevice != null)
    {
      videoRecorderBrush = new VideoBrush();
      videoRecorderBrush.SetSource(captureSource);
      viewfinderRectangle.Fill = videoRecorderBrush;
      captureSource.Start();
      UpdateUI(ButtonState.Initialized, 
        "Tap record to start recording...");
    }
    else
    {
      UpdateUI(ButtonState.CameraNotSupported,
         "A camera is not supported on this device.");
    }
  }
}

Ich will nicht jede Codezeile in Abbildung 3 kommentieren – Sie finden umfassende Hinweise in der Dokumentation (bit.ly/YVIf0I). Aber zusammenfassend lässt sich sagen, dass der Code die VideoCaptureDevice-Instanz initialisiert und dann die Videovorschau auf der Benutzeroberfläche (User Interface, UI) einrichtet. Dadurch, dass ich einfach Codebehind kopiert und in die konkrete Implementierung eingefügt habe, sind einige Probleme aufgetaucht. Der Code enthält Verweise auf UI-Elemente und Methoden (beispielsweise „viewfinderRectangle“ und die UpdateUI-Methode). In Ihrer konkreten Implementierung möchten Sie diese Verweise nicht haben, und hätten Sie bereits ein Ansichtsmodell eingeführt, wären sie leichter zu bearbeiten. Es sind hier also einige Aufräumarbeiten erforderlich:

  1. Verschieben Sie den UI-Code zurück in die jeweilige UI-Methode (in diesem Fall nach „InitializeVideoRecorder“ in der Datei „MainPage.xaml.cs“).
  2. Erstellen Sie eine neue abstrakte Methode zum Initialisieren von „VideoRecorderBrush“, da dies für Windows Phone 7.5 und Windows Phone 8 erforderlich ist, wobei sich die Implementierungen unterscheiden können.

Nach diesen Aufräumarbeiten wird Ihre Methode wie folgt aussehen:

public override void InitializeVideoRecorder()
{
  if (_captureSource == null)
  {
    _captureSource = new CaptureSource();
    _fileSink = new FileSink();
    _videoCaptureDevice = 
      CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
    _captureSource.CaptureFailed +=
       new EventHandler<ExceptionRoutedEventArgs>(OnCaptureFailed);
   }
}

Jetzt stehen Ihnen die Vorteile eines Windows Phone-Codebehind (MainPage.xaml.cs) versionsübergreifend für Windows Phone 7.5 und Windows Phone 8 zur Verfügung, wie in Abbildung 4 dargestellt.

Abbildung 4 – Codebehind sowohl für Windows Phone 7.5 als auch für Windows Phone 8

public void InitializeVideoRecorder()
{
  _videoRecorder.InitializeVideoRecorder();
  _videoRecorder.CaptureFailed += OnCaptureFailed;
  if (_videoRecorder.VideoCaptureDevice != null)
  {
    videoRecorderBrush = new VideoBrush();
    videoRecorderBrush.SetSource(
      _videoRecorder.VideoSource as CaptureSource);
    viewfinderRectangle.Fill = videoRecorderBrush;
    _videoRecorder.StartVideoSource();
    UpdateUI(ButtonState.Initialized,
      "Tap record to start recording...");
  }
  else
  {
    UpdateUI(ButtonState.CameraNotSupported,
       "A camera is not supported on this device.");
  }
}

Sie müssen nur noch die VideoRecorder-Instanz initialisieren und können dann eine neue plattformspezifische Implementierung verwenden.

_videoRecorder = VideoRecorderCore.Instance = new WP7VideoRecorder();

Abgesehen von ein paar kleinen Änderungen können Sie einfach so weitermachen und die Umgestaltung für die übrigen Methoden in ähnlicher Weise durchführen.

Verwenden der neuen Windows Phone 8-APIs

Wie bereits erwähnt, ist die Portierung von Windows Phone 7.5-Code nach Windows Phone 8 sehr einfach – Sie müssen nur das Projekt öffnen und „Upgrade auf Windows Phone 8.0“ auswählen (siehe Abbildung 5). Was aber, wenn Sie beide Versionen verwenden möchten? Entsprechend den bereits vorgenommen Schritten ist der Code nun für eine einfache Wiederverwendung strukturiert. Darüber hinaus haben Sie den Videorekorder entkoppelt und können nun das neue, leistungsstärkere WinRT-API zur Videokameraaufnahme unter Windows Phone 8 verwenden.

Upgrading Your Project to Target the Windows Phone 8 Runtime
Abbildung 5 – Projektupgrade auf die Windows Phone 8-Runtime

Der nächste Schritt ist, das Windows Phone 8-Projekt zu erstellen und dabei so viel Code wie möglich wiederzuverwenden sowie alle gewünschten neuen Funktionen zu unterstützen.

An diesem Punkt haben Sie zwei Optionen zum Erstellen einer Windows Phone 8-Version. Sie können das vorhandene Projekt entweder kopieren und dann ein Upgrade vornehmen, oder Sie können ein neues Windows Phone 8-Projekt erstellen und die übereinstimmenden Codedateien und Verweise einfügen. Die Entscheidung wird davon abhängen, wie groß Ihr Projekt ist und wie viel Code Sie wiederverwenden möchten. In diesem Beispiel kopieren Sie das Projekt und nehmen ein Upgrade vor – Sie picken sich also die wiederverwendbaren Rosinen heraus und vermindern zudem das Risiko, einige wichtige Dateien zu vergessen. Gehen Sie dazu wie folgt vor:

  • Kopieren Sie das sdkVideoRecorderCS-Projekt und geben Sie „sdkVideoRecorderCS.csproj“ den neuen Namen „sdkVideoRecorderCS.WP8.csproj“. Dann fügen Sie das neue Projekt der vorhandenen Projektmappe hinzu, um beide Versionen verwalten zu können.
  • Aktualisieren Sie das neue Projekt auf Windows Phone 8. Sie werden feststellen, dass es dann unter Windows Phone 8 problemlos funktioniert – dankt der API-Rückwärtskompatibilität. Der Vorteil dieser Vorgehensweise ist, dass keine Codeänderungen erforderlich sind. Nachteilig ist die möglicherweise geringere Leistung, und dass Sie die neuen Features der Plattform nicht nutzen können.
  • Sie können nun die Elemente auswählen, die wiederverwendet werden sollen. So bleiben beispielsweise „MainPage.xaml“ und „MainPage.xaml.cs“ in allen Versionen nahezu gleich (zumindest in dieser Projektmappe). Löschen Sie daher diese Dateien im Windows Phone 8-Projekt und fügen Sie einen Dateiverweis auf die Versionen im Windows Phone 7.5-Projekt hinzu, indem Sie Ihr Projekt mit der rechten Maustaste anklicken, die Option “Vorhandenes Element hinzufügen” wählen, dann “Als Verweis hinzufügen” markieren und auf die MainPage.xaml-Datei zeigen (siehe Abbildung 6).

Adding a File Link
Abbildung 6 – Hinzufügen eines Dateiverweises

Anmerkung: Statt wie beschrieben die Menüs zu öffnen, können Sie einfach die MainPage.xaml-Datei aus einem Projekt in ein Zielprojekt ziehen, während Sie die Tasten STRG und UMSCHALT gleichzeitig gedrückt halten. Dadurch wird ein Dateiverweis erstellt und die Codebehind-Datei automatisch verschoben.

Sie müssen die vorangegangenen Schritte für alle relevanten Dateien wiederholen, aber zur Demonstration genügt es, nur diese Datei wiederzuverwenden und den Verweis hinzuzufügen.

Es gibt, nebenbei bemerkt, oft utopische Träume bezüglich die Wiederverwendung von Code. In der realen Welt wird es oftmals so sein, dass Sie übereinstimmenden Code plattformübergreifend verwenden möchten, es aber einen oder zwei feine Unterschiede gibt. Unter diesen Umständen kann es unpraktisch sein, zwei Codeversionen zu warten, und es wäre übertrieben, die Plattformbesonderheiten zu abstrahieren. In einem solchen Scenario ist es oftmals besser, bestimmte Codezeilen auf einer bestimmten Plattform über Präprozessordirektiven anzusprechen. Ein entsprechendes Beispiel folgt später. Wichtig ist, dass Sie diese Technik sparsam einsetzen, um nicht Spaghetticode zu produzieren.

Ein gutes Beispiel für die Verwendung der Plattformabstraktion ist ein isolierter, plattformspezifischer Funktionalitätsblock. In diesem Beispiel ist dies die vorliegende Videorekorderlogik. Bisher enthält Ihr Windows Phone 8-Projekt die Datei „WP7VideoRecorder.cs“, die zwar gut funktioniert, aber nicht das neue Windows Phone 8-API verwendet. Das soll jetzt geändert werden. Löschen Sie die Datei „WP7VideoRecorder.cs“ aus Ihrem Windows Phone 8-Projekt, und erstellen Sie eine neue Datei mit dem Namen „WP8VideoRecorder.cs“, die ebenfalls von „VideoRecorderCore“ erbt.

Implementieren Sie wie zuvor jede der Methoden. Der Hauptunterschied ist diesmal, dass Sie den „Namespace Windows.Phone.Media.Capture“ anstelle von „System.Windows.Media“ verwenden. Der Erstgenannte ist der neue WinRT-Namespace, er enthält die neue Klasse „AudioVideoCaptureDevice“. Diese erfüllt einen ähnlichen Zweck wie die VideoCaptureDevice-Klasse, besitzt aber eine erheblich umfangreichere Funktionalität.

Es gibt mehrere Bereiche, in denen sich die WinRT-APIs von ihren Vorgängern unterscheiden. Viele der APIs sind jetzt asynchron – das ist eine der Änderungen, die hier eine Rolle spielen (mehr dazu später). Ein anderer Unterschied ist, dass Sie zum Speichern jetzt den Windows.Storage-Namespace verwenden und nicht mehr den üblichen „System.IO.IsolatedFileStream“ (obwohl dieser weiterhin zur Unterstützung der Medienwiedergabe dient).

Ich werden jetzt einige der größeren Änderungen bzw. Unterschiede im Videorekorderszenario aufzeigen und etwas allgemeiner erläutern, wie Sie diese Art von Unterschieden behandeln und trotzdem Code wiederverwenden können.

In der Windows Phone 7.5-Version des Videorekordercodes sind etliche private Variablen definiert:

private CaptureSource _captureSource;
private VideoCaptureDevice _videoCaptureDevice;
private IsolatedStorageFileStream _isoVideoFile;
private FileSink _fileSink;
private string _isoVideoFileName = "CameraMovie.mp4";

Idealerweise sollten die beiden Codebasen so übereinstimmend wie möglich bleiben, aber in der neuen API sind „CaptureSource“ und „FileSink“ nicht mehr erforderlich – „CaptureSource“ wird durch die vorliegende VideoCaptureDevice-Instanz ersetzt, die als Quelle dienen kann, und die FileSink-Funktionalität gibt es kostenlos dazu, sodass Sie nur eine „StorageFile“ zum Speichern benötigen:

private AudioVideoCaptureDevice _videoCaptureDevice;
private IsolatedStorageFileStream _isoVideoFile;
private StorageFile sfVideoFile;
private string _isoVideoFileName = "CameraMovie.mp4";

Die folgenden logischen Schritte dienen zur konkreten Implementierung der Methoden. Ich beginne erneut mit „Initialize­VideoRecorder“. Sie wissen, wie diese Methode zuvor ausgesehen hat, und Sie müssen hier einfach die AudioVideoCaptureDevice-Instanz in ähnlicher Weise initialisieren:

public async override void InitializeVideoRecorder()
{
  CameraSensorLocation location = CameraSensorLocation.Back;
  var captureResolutions =
     AudioVideoCaptureDevice.GetAvailableCaptureResolutions(location);
  _videoCaptureDevice =
     await AudioVideoCaptureDevice.OpenAsync(location, 
       captureResolutions[0]);
  _videoCaptureDevice.RecordingFailed += OnCaptureFailed;
}

Wie Sie sehen, ist die Syntax unterschiedlich, aber der Code erfüllt denselben Zweck. In diesem Teil des Codes können Sie auch zusätzliche Kameraeigenschaften konfigurieren, beispielsweise die Aufzeichnungsauflösung und das Kameramodell (falls mehrere Kameras zur Auswahl stehen). Die Möglichkeit zur Auswahl einer Auflösung ist einer der Vorteile gegenüber den Windows Phone 7.5-APIs, da Sie nun das hochauflösende 1080p-Format verwenden können, wenn Ihre Smartphonekamera diese Auflösung unterstützt. Eine andere willkommene Neuerung vieler WinRT-APIs ist, dass ihre Methoden asynchron sind. Zur Unterstützung der Verwaltbarkeit können Sie die C# 5-Schlüsselwörter „async“ und „await“ verwenden. Ich werde hier nicht weiter auf diese Features eingehen, weil sie bereits an anderer Stelle behandelt wurden. Sie sichern sich damit die Vorteile von asynchronem Code bei gleichzeitiger Reduzierung der Komplexität.

Als nächstes betrachten wir die InitializeFileSink-Methode. Auch wenn „FileSink“ an sich nicht mehr vorhanden ist, müssen Sie doch die Dateien initialisieren, in denen die Videoaufzeichnung gespeichert werden soll. Das erfolgt ebenfalls dort, wo Sie den Windows.Storage-Namespace erstmalig verwenden, mit Klassen wie „StorageFolder“ und „StorageFile“.

public async override void InitializeFileSink()
{
  StorageFolder isoStore = ApplicationData.Current.LocalFolder;
  sfVideoFile = await isoStore.CreateFileAsync(
    _isoVideoFileName, 
    CreationCollisionOption.ReplaceExisting);
}

Da dies die größten hervorzuhebenden Unterschiede sind, besteht keine Notwendigkeit, jede Methode zu erläutern. Methoden, die in beiden Versionen gleich bleiben, können Sie in die VideoRecorderCore-Basisklasse verschieben, um ein einheitliches Verhalten der Unterklassen sicherzustellen.

Nachdem alle Änderungen ausgeführt sind, werden Sie einen Buildfehler in folgender Zeile feststellen:

_videoRecorder = VideoRecorderCore.Instance = new WP7VideoRecorder();

Die Ursache ist offensichtlich – der Windows Phone 8-Code verweist auf eine Klasse, die nicht mehr vorhanden ist: „WP7VideoRecorder“. Selbstverständlich müssen Sie auf die neue WP8VideoRecorder-Klasse verweisen, aber in der Datei MainPage.xaml.cs würde diese Änderung zu einem Fehler im Windows Phone 7.5-Code führen, weil Sie dieselbe Datei verknüpfen.

Sie haben nun die Wahl zwischen einigen Optionen. Sie könnten wie folgt Präprozessordirektiven verwenden:

#if WP7
  _videoRecorder = VideoRecorderCore.Instance = new WP7VideoRecorder();
#else
  _videoRecorder = VideoRecorderCore.Instance = new WP8VideoRecorder();
#endif

Sollten Sie Präprozessordirektiven verwenden wollen, wäre der bessere Ansatz, sie in die Wrapperklasse zu verschieben – beispielsweise könnten Sie in der abstrakten Basisklasse „VideoRecorderCore“ eine CreateVideoRecorder-Methode einführen und die vorangehende #if-Deklaration dort hinein verschieben.

Ich habe mich dafür entschieden, Präprozessordirektiven zu vermeiden und die konkrete Klassenstruktur in eine separate Fabrikklasse (factory class) zu abstrahieren. Dadurch ist der Code, der den Rekorder verwendet, sowohl für Windows Phone 7.5 als auch für Windows Phone 8 konsistent.

_videoRecorder =
  VideoRecorderCore.Instance = VideoRecorderFactory.CreateVideoRecorder();

Ein alternativer Ansatz wäre die Verwendung eines Dependency Injection (DI)- oder Service Locator-Entwurfsmusters, wodurch die Instanzauflösung in der Verantwortung des Containers bzw. Dienstes verbleibt.

Ein etwas unscheinbares Problem ist die VideoBrush-Einstellung für die Kameravorschau. Auch in diesem Fall können Sie die Präprozessortechnik mit der #if-Direktive verwenden, um zwischen den kleinen Implementierungsdetails zu unterscheiden:

#if WP7
  videoRecorderBrush.SetSource(_videoRecorder.VideoSource as CaptureSource);
#else
  videoRecorderBrush.SetSource(_videoRecorder.VideoSource);
#endif

Dies ist erforderlich, weil Windows 8 die SetSource-Methode überlädt, damit ein Objekt als Source-Objekt festgelegt werden kann. Dadurch können Sie in Windows Phone 8 die AudioVideoCaptureDevice-Instanz in der VideoSource-Eigenschaft zurückgeben. Voraussetzung ist, dass Sie im Windows Phone 7.5-Projekt ein neues bedingtes Kompilierungssymbol „WP7“ unter Projekteigenschaften|Build hinzufügen (siehe Abbildung 7).

Adding a Custom Conditional Compilation Symbol to Your Windows Phone 7.5 Project
Abbildung 7 – Hinzufügen eines benutzerdefinierten bedingten Kompilierungssymbols zum Windows Phone 7.5-Projekt

Wie bereits erwähnt, verwendet die Windows-Runtime in großem Umfang asynchrone Methoden, um das Antwortzeitverhalten einer App zu optimieren. Welche Auswirkungen hat das auf Code, der auf mehreren Plattformen gemeinsam verwendet werden soll? Obwohl Sie C# 5-Code in einem Projekt für Windows Phone 7.5 verwenden können, gibt es bestimmte Implementierungsdetails, die nicht verfügbar sind, wenn Sie die Schlüsselwörter „async“ und „await“ verwenden. Betrachten Sie folgende Methode:

public async override void InitializeVideoRecorder()
{
  CameraSensorLocation location = CameraSensorLocation.Back;
  var captureResolutions =
     AudioVideoCaptureDevice.GetAvailableCaptureResolutions(location);
  _videoCaptureDevice =
     await AudioVideoCaptureDevice.OpenAsync(location, 
       captureResolutions[0]);
  _videoCaptureDevice.RecordingFailed += OnCaptureFailed;
}

Damit wird die Eigenschaft „VideoCaptureDevice“ zwar faktisch festgelegt, aber der Code, in dem sie verwendet wird (der „konsumierende“ Code), sieht folgendermaßen aus:

_videoRecorder.InitializeVideoRecorder();
if (_videoRecorder.VideoCaptureDevice != null)
{
  ...
}
else
{
  UpdateUI(ButtonState.CameraNotSupported,
     "A camera is not supported on this device.");
}

Möglicherweise ist Folgendes nicht sofort ersichtlich: Während Sie auf den Aufruf der AudioVideoCaptureDevice.OpenAsync-Methode warten, kann die Abfrage nach dem Wert von „_videoRecorder.VideoCaptureDevice“ bereits erfolgt sein. Mit anderen Worten, die Abfrage des VideoCaptureDevice-Werts könnte NULL ergeben, weil das Gerät zu diesem Zeitpunkt noch keine Möglichkeit zur Initialisierung hatte.

Auf dieser Stufe haben Sie wiederum einige Optionen. Sie können den konsumierenden Code asynchron gestalten, also etwas „nachrüsten“. Je nach dem, wie Ihre Lösung beschaffen ist, kann das mit sehr viel Arbeit verbunden sein. Um die zu vermeiden, könnten Sie lediglich den asynchronen Code in eine synchrone Wrappermethode einschließen. Eine andere Alternative wäre ein ereignisbasierter Mechanismus, der den konsumierenden Code benachrichtigt, sobald die Initialisierung erfolgt ist.

Wenn Sie jetzt erwägen, das asynchrone Modell zu verwenden und entsprechend auf den InitializeVideoRecorder-Aufruf zu warten, erhebt sich die Frage, wie das im Windows Phone 7.5-SDK möglich sein soll, das die erforderlichen Konstrukte ja nicht unterstützt. Eine Lösung wäre, in Visual Studio das NuGet-Paket „Async for .NET Framework 4, Silverlight 4 and 5, and Windows Phone 7.5“ herunterzuladen, wie in Abbildung 8 dargestellt.

Find the Async NuGet Package for Windows Phone 7.5 by Searching for “microsoft.bcl.async”
Abbildung 8 – Suchen Sie nach dem Async NuGet-Paket für Windows Phone 7.5 durch Eingabe von „microsoft.bcl.async“

Während ich diesen Artikel schreibe, gibt es erst eine Betaversion des Pakets, aber die Endversion soll in Kürze fertig sein. Sollten Sie den altbewährten Weg bevorzugen, müssen Sie den Code umschreiben (wie zuvor erläutert) und ein Ereignismuster oder einen synchronen Wrapper einfügen.

Anmerkung: Achten Sie darauf, den NuGet Package Manager in der Version 2.1 (oder neuer) zu installieren, weil Sie bei der Verwendung älterer Versionen unerwartete Fehlermeldungen erhalten. Sie können die neueste Version hier herunterladen: bit.ly/dUeqlu.

Nachdem Sie dieses Paket heruntergeladen haben, können Sie den Code in jedem Projekt anpassen. Sie können beispielsweise asynchrone Unterstützung für Ihre abstrakte Basisklasse bereitstellen, indem Sie den Typ „Task“ statt „void“ zurückgeben. Das ermöglicht Ihnen, auf das Ergebnis zu warten, sodass Sie den momentan verwendeten „Fire-and-Forget“-Mechanismus nicht mehr benötigen. Es gehört außerdem zum guten Programmierstil, die Namen der asynchronen Methoden um das Schlüsselwort „Async“ zu erweitern, weil die Methoden dadurch für andere Entwickler intuitiv erkennbar sind. Beispielsweise besitzen einige Methoden jetzt folgende Signatur:

public abstract Task InitializeVideoRecorderAsync();
public abstract Task InitializeFileSinkAsync();

Selbstverständlich müssen Sie dann den Code in den Unterklassen umgestalten. Beispiel:

public override async Task InitializeVideoRecorderAsync()
{

Abschließend sind Sie nun in der Lage, den Code in „MainPage.xaml.cs“ so zu aktualisieren, dass das Warten auf asynchrone Operationen möglich wird. Dazu ein Beispiel:

public async void InitializeVideoRecorder()
{
  await _videoRecorder.InitializeVideoRecorderAsync();

Nachdem Sie diese letzten Änderungen vorgenommen haben, sollten Sie sowohl das Windows Phone 7.5-Projekt als auch das Windows Phone 8-Projekt fehlerfrei ausführen können. Sie konnten Code in erheblichem Umfang wiederverwenden und die neuesten Features nutzen, soweit diese unterstützt werden. Sie haben eine optimale Lösung gefunden!

Wie sieht es mit Windows 8 aus?

Viele meiner Partner haben Windows Phone-Apps mit XAML und C# entwickelt. Wenn es darum geht, eine Windows Store-App für Windows 8 zu entwickeln, ist eine ihrer ersten technischen Fragen, ob HTML5/JavaScript oder XAML/C# besser geeignet ist. Da aus einer Windows Phone-Lösung mit einer guten Architektur sehr viel Code für Windows 8 wiederverwendet werden kann, ist dies möglicherweise ein Entscheidungsfaktor bei der Auswahl der Programmiersprache für Windows Store-Apps.

Aber auch wenn gemeinsamer Code für Windows Phone 7.5 und Windows 8 verwendet werden kann, gibt es trotzdem noch einige Szenarien, in denen Sie beide Versionen unterstützen müssen. Der Videorekorder ist ein solches Szenario. Mit den zuvor dargestellten Lösungswegen können Sie jedoch viel Zeit und Mühe sparen.

Die Implementierung einer Windows 8-Version des Codes überlasse ich dem Leser als Übung. Wenn Sie aber möchten, können Sie das zugehörige Beispiel herunterladen. Es enthält den gesamten besprochenen Code zusammen mit einer Lösung für Windows 8.

Anmerkung: Für eine Windows 8-Implementierung benötigen Sie die Klasse „Windows.Media.Capture.MediaCapture“.

Weitergehende Möglichkeiten mit MVVM

Mit der bisherigen Implementierung haben Sie durch die Wiederverwendung von Code bezüglich „MainPage.xaml“ eine vollständige Übereinstimmung in den beiden Versionen von Windows Phone. Mithilfe der vorgenommenen Umgestaltungs- und Abstraktionsschritte waren Sie zudem in der Lage, auch für die zugehörige Codebehind-Datei nahezu 100 Prozent des Codes wiederzuverwenden. Sie können diese Techniken mit MVVM-Entwurfsmustern noch erweitern und dadurch Code nicht nur für Windows Phone-Versionen, sondern auch für die Windows 8-Codebasis in erheblichem Umfang wiederverwenden.

Diese Techniken ermöglichen die Wiederverwendung von Code durch die Abstraktion plattformspezifischer Features wie die beschriebene Videoaufzeichnung, Anwendungslebenszyklus-Verwaltung (Application Lifecycle Management, ALM), Dateispeicherung usw.

Chris Barker aus Derbyshire in England ist technischer Evangelist für Microsoft. Er hat viele Jahre Code für unabhängige Softwareentwickler und Unternehmen entwickelt, Kurse über Leistung und Skalierbarkeit geleitet und sich intensiv mit Low-Level-Debugging-Techniken befasst. In den letzten Jahren hat er sich mehr auf die Erstellung von Anwendungen mit XAML und C# konzentriert und arbeitet jetzt mit internationalen Partnern als Entwickler für die Plattformen Windows 8 und Windows Phone.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Daniel Plaisted (Microsoft)
Bei Microsoft seit 2008, hat Daniel Plaisted mitgearbeitet am Managed Extensibility Framework (MEF), den Portable Class Libraries (PCL) und dem Microsoft .NET Framework für Windows Store-Apps. Er hat Beiträge geliefert für MS TechEd, BUILD und etliche lokale Gruppen, Code-Camps und Konferenzen. In seiner Freizeit beschäftigt er sich mit Computerspielen, Wandern, Jonglieren und Footbag (Hackey-Sack). Sie finden seinen Blog unter blogs.msdn.com/b/dsplaisted/.