Skip to content
This repository has been archived by the owner on Apr 29, 2024. It is now read-only.

Commit

Permalink
[avcustomedit] Some timing and simplification fixes (#349)
Browse files Browse the repository at this point in the history
Running this sample on device with the interpreter has spotted a few
issues in the sample.

* Timing issue: Use `DispatchGroup.Notify` (like sample) instead of
  `Waiting` (it's not the same) and remove the `InvokeOnMainThread`
  since we're already running on that thread. Also uncomment some
  code that, likely, did not work because `Notify` was not used;

* Timing issue: `composition` can be `null` but it triggers an
  `ArgumentNullException` (because the API does not _officially_ accept
   `nil`).

* Enhancement: Set initial capacity of `List<T>` - just like it's
  done in the original sample;

* Simplification: There's no point in checking for `null` values inside
  `List<T>` since that would have triggered an `ArgumentNullException`;

* Simplification" There's no point in converting `CMTimeRange` into
  `NSValue` (and back into `CMTimeRange`). In contrast to `NSArray`
  we can add non-`NSObject` (including value type like struct) inside
  `List<T>`;

* Cleanup: remove ugly `goto` and use `_` for unused `out NSError`

This will fix xamarin/xamarin-macios#5364
  • Loading branch information
spouliot committed Jan 15, 2019
1 parent beb972a commit df1b3e3
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 58 deletions.
28 changes: 14 additions & 14 deletions AVCustomEdit/AVCustomEdit/SimpleEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ public class SimpleEditor : NSObject

public List<AVAsset> Clips { get; set; } // array of AVURLAssets

public List<NSValue> ClipTimeRanges { get; set; } // array of CMTimeRanges stored in NSValues.
public List<CMTimeRange> ClipTimeRanges { get; set; }

public TransitionType TransitionType { get; set; }

public CMTime TransitionDuration { get; set; }

public AVPlayerItem PlayerItem => new AVPlayerItem(this.composition) { VideoComposition = this.videoComposition };
public AVPlayerItem PlayerItem {
get {
if (composition == null)
return null;
return new AVPlayerItem(this.composition) { VideoComposition = this.videoComposition };
}
}

private void BuildTransitionComposition(AVMutableComposition mutableComposition, AVMutableVideoComposition mutableVideoComposition)
{
Expand All @@ -30,12 +36,9 @@ private void BuildTransitionComposition(AVMutableComposition mutableComposition,
var transitionDuration = this.TransitionDuration;
foreach (var clipTimeRange in this.ClipTimeRanges)
{
if (clipTimeRange != null)
{
var halfClipDuration = clipTimeRange.CMTimeRangeValue.Duration;
halfClipDuration.TimeScale *= 2; // You can halve a rational by doubling its denominator.
transitionDuration = CMTime.GetMinimum(transitionDuration, halfClipDuration);
}
var halfClipDuration = clipTimeRange.Duration;
halfClipDuration.TimeScale *= 2; // You can halve a rational by doubling its denominator.
transitionDuration = CMTime.GetMinimum(transitionDuration, halfClipDuration);
}

// Add two video tracks and two audio tracks.
Expand All @@ -55,16 +58,13 @@ private void BuildTransitionComposition(AVMutableComposition mutableComposition,
{
int alternatingIndex = i % 2; // alternating targets: 0, 1, 0, 1, ...
var asset = this.Clips[i];
var clipTimeRange = this.ClipTimeRanges[i];

var timeRangeInAsset = clipTimeRange != null ? clipTimeRange.CMTimeRangeValue
: new CMTimeRange { Start = CMTime.Zero, Duration = asset.Duration };
var timeRangeInAsset = this.ClipTimeRanges[i];

var clipVideoTrack = asset.TracksWithMediaType(AVMediaType.Video)[0];
compositionVideoTracks[alternatingIndex].InsertTimeRange(timeRangeInAsset, clipVideoTrack, nextClipStartTime, out NSError error);
compositionVideoTracks[alternatingIndex].InsertTimeRange(timeRangeInAsset, clipVideoTrack, nextClipStartTime, out _);

var clipAudioTrack = asset.TracksWithMediaType(AVMediaType.Audio)[0];
compositionAudioTracks[alternatingIndex].InsertTimeRange(timeRangeInAsset, clipAudioTrack, nextClipStartTime, out error);
compositionAudioTracks[alternatingIndex].InsertTimeRange(timeRangeInAsset, clipAudioTrack, nextClipStartTime, out _);

// Remember the time range in which this clip should pass through.
// First clip ends with a transition.
Expand Down
65 changes: 21 additions & 44 deletions AVCustomEdit/AVCustomEdit/ViewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public partial class ViewController : UIViewController, IUIGestureRecognizerDele
private SimpleEditor editor;

private List<AVAsset> clips;
private List<NSValue> clipTimeRanges;
private List<CMTimeRange> clipTimeRanges;

private AVPlayer player;
private AVPlayerItem playerItem;
Expand All @@ -44,8 +44,8 @@ public override void ViewDidLoad()
base.ViewDidLoad();

this.editor = new SimpleEditor();
this.clips = new List<AVAsset>();
this.clipTimeRanges = new List<NSValue>();
this.clips = new List<AVAsset>(2);
this.clipTimeRanges = new List<CMTimeRange>(2);

// Defaults for the transition settings.
this.transitionType = TransitionType.DiagonalWipeTransition;
Expand Down Expand Up @@ -115,37 +115,42 @@ private void SetupEditingAndPlayback()
this.LoadAsset(asset2, assetKeysToLoadAndTest, dispatchGroup);

// Wait until both assets are loaded
dispatchGroup.Wait(DispatchTime.Forever);
base.InvokeOnMainThread(() => this.SynchronizeWithEditor());
dispatchGroup.Notify(DispatchQueue.MainQueue, () => {
SynchronizeWithEditor();
});
}

private void LoadAsset(AVAsset asset, string[] assetKeysToLoad, DispatchGroup dispatchGroup)
{
dispatchGroup.Enter();
asset.LoadValuesAsynchronously(assetKeysToLoad, () =>
{
bool add_asset = true;
// First test whether the values of each of the keys we need have been successfully loaded.
foreach (var key in assetKeysToLoad)
{
if (asset.StatusOfValue(key, out NSError error) == AVKeyValueStatus.Failed)
{
Console.WriteLine($"Key value loading failed for key:{key} with error: {error?.LocalizedDescription ?? ""}");
goto bail;
add_asset = false;
break;
}
}
if (!asset.Composable)
{
Console.WriteLine("Asset is not composable");
goto bail;
add_asset = false;
}
this.clips.Add(asset);
// This code assumes that both assets are atleast 5 seconds long.
var value = NSValue.FromCMTimeRange(new CMTimeRange { Start = CMTime.FromSeconds(0, 1), Duration = CMTime.FromSeconds(5, 1) });
this.clipTimeRanges.Add(value);
if (add_asset)
{
this.clips.Add(asset);
// This code assumes that both assets are atleast 5 seconds long.
var value = new CMTimeRange { Start = CMTime.FromSeconds(0, 1), Duration = CMTime.FromSeconds(5, 1) };
this.clipTimeRanges.Add(value);
}
bail:
dispatchGroup.Leave();
});
}
Expand Down Expand Up @@ -185,8 +190,8 @@ private void SynchronizePlayerWithEditor()
private void SynchronizeWithEditor()
{
// Clips
this.SynchronizeEditorClipsWithOurClips();
this.SynchronizeEditorClipTimeRangesWithOurClipTimeRanges();
this.editor.Clips = new List<AVAsset> (clips);
this.editor.ClipTimeRanges = new List<CMTimeRange> (clipTimeRanges);

// Transitions
if (this.isTransitionsEnabled)
Expand All @@ -200,36 +205,8 @@ private void SynchronizeWithEditor()
}

// Build AVComposition and AVVideoComposition objects for playback
//this.editor.BuildCompositionObjectsForPlayback(true);
//this.SynchronizePlayerWithEditor();
}

private void SynchronizeEditorClipsWithOurClips()
{
var validClips = new List<AVAsset>();
foreach (var asset in this.clips)
{
if (asset != null)
{
validClips.Add(asset);
}
}

this.editor.Clips = validClips;
}

private void SynchronizeEditorClipTimeRangesWithOurClipTimeRanges()
{
var validClipTimeRanges = new List<NSValue>();
foreach (var timeRange in this.clipTimeRanges)
{
if (timeRange != null)
{
validClipTimeRanges.Add(timeRange);
}
}

this.editor.ClipTimeRanges = validClipTimeRanges;
this.editor.BuildCompositionObjectsForPlayback(true);
this.SynchronizePlayerWithEditor();
}

#endregion
Expand Down

0 comments on commit df1b3e3

Please sign in to comment.