Flex: Restore Spark VideoDisplay stream - apache-flex

EDIT
If someone can at least tell me how to receive an event when the streams disconnects that would be great.
The documentation for this control is simply horrible. I have an application that will have a live video stream and I'm looking for a way to make the VideoDisplay control restore its connection in case of the occurrence of any of these specific scenarios:
The application starts and the stream is not online yet.
The application is streaming and the user is disconnected from the internet.
The application is streaming and the video server crashes and reboots.
I'm using Wowza Media Server and Wirecast to test this. 1 and 3 don't work, I'm not sure number 2 does. I made number 1 work by adding this very questionable piece of code:
protected function onMediaPlayerStateChange(event:MediaPlayerStateChangeEvent):void
{
if (event.state == MediaPlayerState.PLAYBACK_ERROR)
{
var videoSource:DynamicStreamingVideoSource = this.videoDisplay.source as DynamicStreamingVideoSource;
try
{
this.videoDisplay.source = null;
this.videoDisplay.source = videoSource;
}
catch (any:*) {}
}
}
As you can see I need a try/catch block since both calls to source cause exceptions, yet whatever happens before those exceptions seems to fix problem #1. This doesn't fix problem #3 because a media state change event apparently doesn't occur when you stop the video server.
This is my control declaration:
<s:VideoDisplay id="videoDisplay" click="onVideoStreamClick(event)" mediaPlayerStateChange="onMediaPlayerStateChange(event)" muted="{this.videoMuted}" top="10" width="280" height="220" autoPlay="true" horizontalCenter="0">
<s:source>
<s:DynamicStreamingVideoSource id="videoSource" streamType="live" host="{FlexGlobals.topLevelApplication.parameters.videoStreamURL}">
<s:DynamicStreamingVideoItem id="videoItemLow" streamName="{FlexGlobals.topLevelApplication.parameters.videoLow}" bitrate="{FlexGlobals.topLevelApplication.parameters.videoLowBitrate}" />
<s:DynamicStreamingVideoItem id="videoItemMedium" streamName="{FlexGlobals.topLevelApplication.parameters.videoMedium}" bitrate="{FlexGlobals.topLevelApplication.parameters.videoMediumBitrate}" />
<s:DynamicStreamingVideoItem id="videoItemHigh" streamName="{FlexGlobals.topLevelApplication.parameters.videoHigh}" bitrate="{FlexGlobals.topLevelApplication.parameters.videoHighBitrate}" />
</s:DynamicStreamingVideoSource>
</s:source>
</s:VideoDisplay>
Does anyone know how to make the VideoDisplay recover from these issues? Any help is appreciated, thanks.

If anyone has this problem, this is how I solved it. You need to set the video source to a blank image in order to stop the streaming, otherwise it will never, ever stop. This solution works for all scenarios described above:
private function resetVideo():void
{
//save current source object
this.videoEventsDisabled = true;
var videoSource:DynamicStreamingVideoSource = this.videoDisplay.source as DynamicStreamingVideoSource;
try //switch to blank image, only this will stop the video stream
{
this.videoDisplay.source = "assets/images/video_offline.png";
}
catch (any:*) {}
//wait a few seconds and reset video source
setTimeout(resetVideoSource, 2000, videoSource);
}
private function resetVideoSource(videoSource:DynamicStreamingVideoSource):void
{
this.videoEventsDisabled = false;
this.videoDisplay.source = videoSource;
}
protected function onMediaPlayerStateChange(event:MediaPlayerStateChangeEvent):void
{
if (this.videoEventsDisabled)
{
return;
}
//something went wrong
if (event.state == MediaPlayerState.PLAYBACK_ERROR)
{
resetVideo();
}
}
protected function onCurrentTimeChange(event:TimeEvent):void
{
if (this.videoEventsDisabled)
{
return;
}
//if there was a number before, and its suddendly NaN, video is offline
if (isNaN(event.time) && !isNaN(this.previousVideoTime))
{
resetVideo();
}
else //store event time for future comparisons
{
this.previousVideoTime = event.time;
}
}
MXML:
<s:VideoDisplay id="videoDisplay" click="onVideoStreamClick(event)" mediaPlayerStateChange="onMediaPlayerStateChange(event)" currentTimeChange="onCurrentTimeChange(event)" muted="{this.videoMuted}" top="10" width="280" height="220" autoPlay="true" horizontalCenter="0">
<s:source>
<s:DynamicStreamingVideoSource id="videoSource" streamType="live" host="{FlexGlobals.topLevelApplication.parameters.videoStreamURL}">
<s:DynamicStreamingVideoItem id="videoItemLow" streamName="{FlexGlobals.topLevelApplication.parameters.videoLow}" bitrate="{FlexGlobals.topLevelApplication.parameters.videoLowBitrate}" />
<s:DynamicStreamingVideoItem id="videoItemMedium" streamName="{FlexGlobals.topLevelApplication.parameters.videoMedium}" bitrate="{FlexGlobals.topLevelApplication.parameters.videoMediumBitrate}" />
<s:DynamicStreamingVideoItem id="videoItemHigh" streamName="{FlexGlobals.topLevelApplication.parameters.videoHigh}" bitrate="{FlexGlobals.topLevelApplication.parameters.videoHighBitrate}" />
</s:DynamicStreamingVideoSource>
</s:source>
</s:VideoDisplay>

As a variant you may handle NetStream.Play.PublishNotify from NetStream object.
var _source:DynamicStreamingResource;
_source = new DynamicStreamingResource("rtmp://...", StreamType.LIVE);
var streamItems:Vector.<DynamicStreamingItem> = new Vector.<DynamicStreamingItem>();
streamItems.push(new DynamicStreamingItem(streamName, 0));
_source.streamItems = streamItems;
_rtmpDynamicStreamingNetLoader = new RTMPDynamicStreamingNetLoader();
_rtmpDynamicStreamingNetLoader.addEventListener(LoaderEvent.LOAD_STATE_CHANGE, rtmpDynamicStreamingNetLoaderStateChangeHandler);
var cvp:VideoDisplay = new VideoDisplay();
_source.mediaType = MediaType.VIDEO;
var videoElement:MediaElement = new VideoElement(_source, _rtmpDynamicStreamingNetLoader);
cvp.source = videoElement;
private function rtmpDynamicStreamingNetLoaderStateChangeHandler(event:LoaderEvent):void
{
var netStream:NetStream = event.loadTrait["netStream"] as NetStream;
if (netStream != null && !netStream.hasEventListener(NetStatusEvent.NET_STATUS)) {
netStream.addEventListener(NetStatusEvent.NET_STATUS, netStreamNetStatusHandler, false, 0, true);
}
}
private function netStreamNetStatusHandler(event:NetStatusEvent):void
{
if (event.info.code == "NetStream.Play.UnpublishNotify") {
}
if (event.info.code == "NetStream.Play.PublishNotify") {
}
}

I used Johnatan's code along with JayPea's idea to solve my problem!
I used all that Johnatan gave, with the following small change in the netStreamNetStatusHandler():
private function netStreamNetStatusHandler(event:NetStatusEvent):void
{
trace(event.info.code);
if (event.info.code == "NetStream.Play.PublishNotify")
{
try
{
cvp.source = ''; //Doesn't need to be a valid path. Empty string should suffice.
}
catch (error:Error)
{
trace('Video source error: ' + error.message);
}
cvp.source = videoElement;
}
}
This resets the source when the stream stops and restarts, thus causing the video display to play again.
NOTE: The video display's autoPlay property needs to be set as 'true'.

I know this question was asked many years ago, but it still helped me solving my problem encouraging me diving into the sources of VideoDisplay (since there obviously is no "clean" solution ;-(
And for those who have the same problem my "solution" ,ight be helpful too:
I had to restart the same video after the user clicked abort but then decided to play it again. Without "resetting" VideoDisplay this resulted in a black screen.
Here's how I solved the issue after I found out that in VideoDisplay an internal function "setupSource()" was called when in the commitProperties() function no thumnailSource was defined.
Since the setupSource() function was not publicly accessible I used this:
myVideoDisplaySpark.enabled = false; // changes properties
myVideoDisplaySpark.source = ""; // does not harm ;-)
myVideoDisplaySpark.mx_internal::thumbnailSource = null; // forces a setupSource in commitProperties
myVideoDisplaySpark.enabled = true; // finally results in the call if setupSource
That's at least what I understood; and at the end it worked for me ;-)

Related

Xamarin Forms - Duplicate Screens

I'm writing an app using Xamarin Forms and I have an issue I was hoping someone can help with.
My app contains a screen which has multiple icons that can be pressed which would then open a new screen.
My issue is that if you press the icon twice really fast, the app opens up 2 instances of the same screen (it's not just related to a double press, if you press the icon 6 times very fast it will open up 6 duplicate screens). Pressing the Back button, closes the top screen to reveal the duplicate screen underneath. Pressing the Back button again navigates you back to the original screen.
This issue seems to occur on any screen within my app so I'm hoping other people will have experienced it and know of a solution to prevent duplicate screens being displayed.
This is a known issue in TapEvents.
My hack is, in the code-behind, have a bool variable _canTap.
Inside the method you are calling to push new page, first you check if canTap, then set to false, and only set to true after navigating to another page. This way all taps will be disregarded.
Example:
private bool _canTap = true;
public void YourMethod()
{
if(_canTap)
{
_canTap = false;
YourMethodToNavigate();
_canTap = true;
}
}
In the Icon_Pressed method add this,
this.IsEnabled = false;
await Navigation.PushAsync(new MyPage());
this.IsEnabled = true;
It disables the page until the current Icon pressed event is finished
This is known problem with Xamarin apps. I've used a private variable combined with a try-finally pattern to solve this. Ex:
bool allowTap = true;
public void ButtonTapped()
{
try
{
if(allowTap)
{
allowTap = false;
// Do whatever...
}
}
finally
{
allowTap = true;
}
}
The finally makes sure allowTap gets set back to true no matter what happens, short of a complete crash. Note that you can also use a catch block between the try and finally blocks to grab any errors if needed.

Adobe Air Mobile + Camera - Retaining image

I have a page that displays data about an object. At the top of the page is room for an icon, showing a picture of that object. Tapping this icon brings up a new page that allows the user to take a new picture, and save it as a temporary new picture for the object (not put in database, but should persist for the session)
Initial page:
private var source:Object = new Object();
protected function onInitialize():void {
source = navigator.poppedViewReturnedObject;
}
When setting source for the image later...
if (source != null) {
pic.source = source.object;
}
else {
pic.source = "no_picture_available_image.png";
}
2nd Page (User can take picture, and view new picture):
[Bindable]
private var imageSource:Object = null;
<s:Image id="pic" width="90%" height="75%" horizontalCenter="0" source="{imageSource}" />
After taking picture...
protected function mediaPromiseLoaded(evt:Event):void {
var loaderInfo:LoaderInfo = evt.target as LoaderInfo;
imageSource = loaderInfo.loader;
}
This does show the picture just taken correctly on this page.
To get back to old page, i use navigator.popView, and use:
override public function createReturnObject():Object {
return imageSource;
}
Unfortunately, it doesn't work. The imageSource isn't null when it is read from navigator.poppedViewReturnedObject, but no image is shown.
Does the LoaderInfo not persist after popping the view? Are the camera pics not automatically saved? I can't find answers to any of these questions, and I can't debug using the phone in my current environment.
After thinking about this a bit, don't return LoaderInfo.loader as the poppedViewReturnedObject. If I remember correctly, a DisplayObject can only be set as the source of one Image. Instead, return LoaderInfo.loader.content.bitmapData. That BitmapData should be the raw data used to display the image. This data can be used repeatedly to create images and can be set as the source of an Image.
Problem turned out to be in my first page's image declaration - I didn't set a width. Seemingly, the object being displayed couldn't handle not having a specified width.
Note that passing back the loader did work fine.

Expanded Branch do not show its children for first time in Flex Tree?

I have added an event handler for itemOpening event for the tree component inside the handler basically ido myTree.selectedItem = event.item ; and then add the new data inside myTree.selectedItem.children.push(newData);
But It do not show simultaneously instead I have to close , open the branch again to see the new data . I think I need to refresh something after adding new data but dont know what ?
below is the code but without the declaration , script tag etc
<services:DocumentService id="$document"/>
<s:CallResponder id="$newFolderAdded" result="$newFolderAdded_resultHandler(event)"/>
<mx:Tree id="myTree" width="100%" height="60%" creationComplete="myTree_creationCompleteHandler(event)"
labelField="name" itemOpening="myTree_itemClickHandler(event)" />
protected function myTree_itemClickHandler(event:TreeEvent):void
{
myTree.selectedItem = event.item;
if(event.item.hasOwnProperty('children'))
{
$newFolderAdded.token = $document.getDirectDecendents(event.item);
}
}
protected function $newFolderAdded_resultHandler(event:ResultEvent):void
{
for each(var folder:Object in event.result)
{
myTree.selectedItem.children.push(Object(folder));
}
}
Try invalidating the data of the tree by calling:
myTree.invalidateList();

AsyncFileUpload control

Regarding the AsyncFileUpload control in .net, the control will execute the file upload once i select a file. In my concern is, is it possible to disable the upload once i select a file so that i could process the upload asynchronously with a submit button.
I know this is old, but I beat my head on this for a while, so for whoever might be interested.
My first thought was to disable the file input underneath the control.
I was able to disable the control but unfortunately, it stopped working. When the server fired AsyncFileUpload_UploadComplete the input was diabled so there wasn't a file to read.
<script>
function disableFileUpload(on) {
if (on) {
$('#ajax-file-input input:file').attr('disabled', true);
} else {
$(#ajax-file-input 'input:file').attr('disabled', false);
}
}
function AsyncFileUpload_CheckExtension(sender, args) {
disableFileUpload(true);
return true;
}
function AsyncFileUpload_OnClientUploadComplete(sender, args) {
disableFileUpload(false);
var data = eval('(' + args.d + ')');
for (var key in data) {
var obj = data[key];
for (var prop in obj) {
console.log(prop + " = " + obj[prop]);
}
}
doThingsWith(data);
}
</script>
<div id="ajax-file-input">
<ajaxToolkit:AsyncFileUpload ID="AsyncFileUpload1"
OnUploadedComplete="AsyncFileUpload_UploadComplete"
OnClientUploadStarted="AsyncFileUpload_CheckExtension"
OnClientUploadComplete="AsyncFileUpload_OnClientUploadComplete"
runat="server" />
</div>
I ended up positioning a semi-transparent png on top of the control and showing and hiding it to make the control innaccesible.
Hope this helps.
function disableFileUpload(on) {
if (on) {
$("#file-disabled").show();
} else {
$("#file-disabled").hide();
}
}
Simple answer is No. I've had similar asyncupload issues just like those ones. My advice is to stay away from him if you need to control upload with a button, add and remove selected files (you will probably need this later on) and use some javascript manipulation.
Search for the SWFUpload, is a flash component that can be integrated with .NET with ease. It offers multiple javascript options and events. :D
Check the following links:
Official site
Demonstration
As far as I know that the only event exposed by AsyncFileUpload is the UploadComplete event and UploadError. There aren't events specifically that expose functionality to manually initiate the upload. Perhaps some trick in JavaScript could do it but I have not seen such a workaround before.

Spark TextInput incomplete value using Barcode Reader

working with Flex4 AIR app, using this component I get data from the barcode reader,
<s:TextInput id="barcode" enter="showBarcode()"/>
Then for handle the enter event, wich is automatically triggered when reader finishes its reading
private function showBarcode():void{
Alert.show(barcode.text);
}
Ok, very simple. But my problem is: the text showed in the Alert Box is incomplete, it misses one or two digits (last) or it just shows the entire text in the TextInput component (correct view)
I already test my BC reader using the notepad, and it's fine.
I have tested same code using MX components in Flex3 web app, and there's no problem.
Some help will be appreciated.
Tnx
We ran into the same problem and we ended up saving the chars received from the barcode scanner in a buffer instead of accessing the TextInput text property. We always received the chars in the correct order but, with the Spark TextInput, the text property was sometimes scrambled.
<s:TextInput id="barcode" keyDown="barcode_keyDownHandler(event)" />
This is the buffer we used and these are the relevant functions:
private var textBuffer:ArrayList = new ArrayList();
protected function getTextBufferContent():String
{
var content:String="";
for (var i:int = 0; i < textBuffer.length; i++)
{
content=content.concat(textBuffer.getItemAt(i));
}
return content;
}
protected function handleKeyboardEnter():void
{
var barcodeScan:String=getTextBufferContent();
textBuffer.removeAll();
if (barcodeScan != "")
{
Alert.show(barcodeScan);
}
}
protected function barcode_keyDownHandler(event:KeyboardEvent):void
{
trace("barcode_keyDownHandler: " + event.keyCode + " " + String.fromCharCode(event.keyCode));
if (event.keyCode == Keyboard.ENTER)
{
handleKeyboardEnter();
}
else textBuffer.addItem(String.fromCharCode(event.keyCode));
}
The mx:TextInput also worked, but we wanted to keep the Spark component.
Instead of using "enter" try listening to the onKeyUp event.
You'll have to figure out the appropriate end of line character read in from the bar code scanner. But, just key in on that and do your alert that way; otherwise do nothing.
Is text at least displayed as you would expect in Spark TextInput by using the same workflow with barcode reader as shown in this video "Flash Nicey Nice With HID" http://www.youtube.com/watch?v=sEw0RB-Uj00 ?
Anyway, I do not think Adobe Flash player supports officially other HID devices rather then keyboard or mouse.
Thus I would not expect Adobe engineers to make Spark TextInput compliant with your barcode reader input any time soon.
We ran into this same thing where I work, with barcode scanning in Flex 4 / Spark. There are two workarounds we found.
The first is to use the old "Halo" TextInput control (<mx:TextInput />). You can do this even in a Flex 4 project.
The second method, is to keep the "Spark" TextInput control, but to use a very short Timer in your keyUp or keyDown event to let the text property be fully filled with the barcode scan data -- I don't know exactly why this works, but it does. And you get to keep all of the benefits of the Spark control/skinning. So the original poster's example could be fixed if it became:
<s:TextInput id="barcode" keyDown="showBarcode(event)"/>
and
private function showBarcode(evt:KeyboardEvent):void {
var t:Timer = new Timer(10, 1); // 10ms
t.addEventListener(TimerEvent.TIMER, function():void {
Alert.show(barcode.text);
});
t.start();
}
Bonus:
If you didn't want to use this Timer all over your code in event handlers, you could make a utility function for it in a Barcoder.as class like so:
public static function checkScan(evt:KeyboardEvent, callback:Function):void
{
if (evt.keyCode == Keyboard.ENTER)
{
var t:Timer = new Timer(10, 1); // 10 ms
t.addEventListener(TimerEvent.TIMER, function():void { callback(); } );
t.start();
}
}
Then, if you had two separate barcode fields, with two separate handlers (bc1_handler() and bc2_handler()) your mxml would be something like:
<s:TextInput id="barcode1" keyDown="Barcoder.checkScan(event, bc1_handler)" />
<s:TextInput id="barcode2" keyDown="Barcoder.checkScan(event, bc2_handler)" />

Resources