Flex: Тотальный контроль над загружаемым swf-клипом во Flex-приложении

Задачи:

  1. В Flex-приложении динамически подгрузить swf-анимацию и flv-видео (то есть разнотипный медиа-контент)
  2. Запускать воспроизведение медиа-контента по нажатию кнопки в приложении
  3. Проигрывать анимацию один раз

Решение:

<?xml version="1.0" encoding="utf-8"?>
<s:SkinnableContainer xmlns:fx="http://ns.adobe.com/mxml/2009"
					  xmlns:s="library://ns.adobe.com/flex/spark"
					  xmlns:mx="library://ns.adobe.com/flex/mx">
	<s:layout>
		<s:VerticalLayout gap="15"/>
	</s:layout>
	<fx:Declarations>
		<fx:XML id="xmlLetters" source="letters.xml" />
	</fx:Declarations>

	<fx:Script>
		<![CDATA[
			import flash.utils.getQualifiedClassName;

			import mx.controls.Image;
			import mx.controls.SWFLoader;
			import mx.controls.Text;
			import mx.events.FlexEvent;

			import ru.asmon.skins.DemostrateButtonSkin;
			import ru.asmon.skins.MediaSkin;

			import spark.components.Label;
			import spark.components.VideoDisplay;

			private var webServerAdress:String = "http://example.com";

			/**
			 * Extract neccery data from XML
			 * and show it in letter_details VGroup
			 */
			public function showLetterDetails(letter:Letter):void {
				// ... some code ...

				//get media files
				letter_media_stuff.removeAllElements();
				for each (var file:XML in xmlLetter.media.file) {
					//get file extension
					var extension:String = file.toString();
					extension = extension.substring(extension.lastIndexOf('.') + 1);
					var mediaContainer:SkinnableContainer = new SkinnableContainer;

					switch (extension) {
						case 'flv': var flv:VideoDisplay = new VideoDisplay;
									flv.source = webServerAdress + file.toString(); //it's link to media file
									flv.width = 130;
									flv.height = 100;
									flv.autoPlay = false;
									mediaContainer.addElement(flv);
									break;
						case 'swf': var swf:SWFLoader = new SWFLoader;
									swf.source = webServerAdress + file.toString();
									swf.width = 130;
									swf.height = 100;
									mediaContainer.addElement(swf);
									//it auto-starts, so stop it!
									swf.addEventListener(Event.INIT, onMediaSwfLoad);
									break;
					}

					letter_media_stuff.addElement(mediaContainer);
				}
			}

			/**
			 * Wait for loading swf and then STOP it!
			 */
			private function onMediaSwfLoad(e:Event):void {
				var swf:SWFLoader = e.target as SWFLoader;
				MovieClip(swf.content).stop();
			}

			/**
			 * Play all media files on button click
			 */
			protected function playButton_clickHandler(event:MouseEvent):void
			{
				for (var i:Number = 0; i < letter_media_stuff.numElements; i++) {
					var container:SkinnableContainer = letter_media_stuff.getElementAt(i) as SkinnableContainer;
					switch (flash.utils.getQualifiedClassName(container.getElementAt(0))) {
						case "spark.components::VideoDisplay":
							var flv:VideoDisplay = container.getElementAt(0) as VideoDisplay;
							flv.play();
							break;
						case "mx.controls::SWFLoader":
							var swf:SWFLoader = container.getElementAt(0) as SWFLoader;
							MovieClip(swf.content).play();
							swf.addEventListener(Event.ENTER_FRAME, stopSwfOncePlayed);
							break;
					}
				}
			}

			/**
			 * Wait for final frame in swf and stop it!
			 */
			private function stopSwfOncePlayed(e:Event):void {
				var  mc:MovieClip = e.target.content as MovieClip;
				if (mc.currentFrame == mc.totalFrames) {
					mc.stop();
					SWFLoader(e.target).removeEventListener(Event.ENTER_FRAME, stopSwfOncePlayed);
				}
			}

		]]>
	</fx:Script>

	<s:HGroup id="letter_media_stuff" gap="20" />
	<s:VGroup id="letter_details" gap="15" />
	<s:Label id="sound_commetary" />
	<s:Button id="playButton"
			  label="Воспроизвести анимации"
			  click="playButton_clickHandler(event)"
			  useHandCursor="true"
			  buttonMode="true"/>
</s:SkinnableContainer>

Чего тут интересного: в отличие от флеш-видео (flv), ходом воспроизведения которого довольно просто управлять (особенно средствами класса VideoDisplay), swf-анимация не имеет методов play(), playOnce(), stop(), pause() и прочее, т.е. нативно нельзя управлять ходом воспроизведения загружаемой swf-анимации.
В коде выше это проблема решается так:

  1. С помощью SWFLoader из внешнего источника загружается swf-анимация
  2. Вешается обработчик события на завершение загрузки swf-файла
  3. Происходит кастинг объекта в MovieClip и остановка воспроизведения
  4. При нажатии на кнопку опять попадается объект SWFLoader, который опять преобразуется в MovieClip и воспроизводится. При этом вешается обработчик события «при вхождении в новый кадр swf-анимации», в котором проверяется, совпадает ли номер текущего проигрываемого кадра с последним (т.е. достигнут ли конец swf-анимации). Если да, то воспроизведение останавливается. Таким образом swf-анимация проигрывается один раз.