2

I'm trying to remove a Google swiffy (v 5.2.0) animation and then readd it at a later date.

In terms of the running animation there doesn't seem to be any problem, but the code triggers an error: TypeError: Cannot redefine property: Animation_fla.MainTimeline at which point all AS3 in the movie stops working. This seems to be because the destroy method is not removing references to the AS3 code within the swiffy runtime. I've spent some time trying to step through the code but it's pretty incomprehensible.

Below is a stripped out version of all I'm doing with swiffy - calling init again after calling destroy will trigger this TypeError. I've tried to reinitialise the swiffy runtime itself but this causes a similar error.

var stage;

function init() {
    stage = new swiffy.Stage(domElement, swiffyJson);
    stage.start();
}

function destroy() {
    stage.destroy();
    stage = null;
} 
baseten
  • 1,362
  • 1
  • 13
  • 34

2 Answers2

1

The only solution I've come up with is a pretty horrible hack. It seem Swiffy really doesn't like to recreate animations after they've been destroyed. I've had some success with detaching the Swiffy from the DOM but retaining it's reference in memory. I then had to hack the runtime to enable pause and restart. I could then reattach the original Swiffy without having to destroy it. This is in v5.2.0 downloaded from https://www.gstatic.com/swiffy/v5.2/runtime.js, after putting the runtime through jsbeautifier.org

Around line 5640 after the M.releaseCapture function I added the following function:

M.hackPause = function (bool) {
    if(this.hackPaused === bool) return;

    if(this.hackPaused) {
        bi(ef(this.qh.yl, this.qh));
    }

    this.hackPaused = bool;
};

The around line 7137, replace the AK[I].yl function with the following:

Ak[I].yl = function () {
    if (this.bh) {
        var a = Date.now();
        a >= this.Mf && (this.tl.ei(), this.Mf += (s[Xb]((a - this.Mf) / this.sl) + 1) * this.sl);
        this.tl.lc();

        if(!this.tl.hackPaused) {
            bi(ef(this.yl, this))
        }
    }
};

What this is doing is preventing the requestAnimationFrame or setTimeout from firing and therefore effectively pausing the animation.

I've tried to expose a gotoAndStop function within the runtime also, but I couldn't manage to find the scope amongst the code. In the meantime, using a hack from this post - Is it possible to pause/resume/manipulate a swiffyobject from JS? we can do this with a brute force approach by adding an enter frame event to the flash movie and testing for a change in Flashvars. Below is the Document Class we've been using for our animations. It's worth noting that Swiffy doesn't seem to like AS3 Classes that extend from the same base class, it throws the same cannot redefine property error, so we've duplicated the code from here in the Document class of each of our Flash animations. I've also got a method in there that allows dispatching events from the AS3 to a javascript function called onSwiffyEvent:

package {

    import flash.display.MovieClip;
    import flash.net.URLRequest;
    import flash.net.navigateToURL;
    import flash.events.Event;
    import flash.utils.setTimeout;

    public class BaseAnimation extends MovieClip {

        private var _request:URLRequest;
        private var _pageName:String;
        private var _movieName:String;

        public function BaseAnimation() {
            _request = new URLRequest();

            _pageName = getFlashVar('pageName');
            _movieName = getFlashVar('movieName');

            addEventListener(Event.ENTER_FRAME, jsListen);
        }

        private function getFlashVar(name:String):String {
            var flashVar:String = stage.loaderInfo.parameters[name] || '';
            stage.loaderInfo.parameters[name] = '';

            return flashVar;
        }

        public function dispatchJSEvent(eventName:String, param:String = ''):void {
            _request.url = "javascript:onSwiffyEvent('" + eventName + "', '" + param + "', '" + _movieName + "', '" + _pageName + "');";
            navigateToURL(_request, "_self");
        }

        private function jsListen(e:Event):void {
            var mode:String = getFlashVar('mode');

            if(mode.length) {
                switch(mode) {
                    case 'stop':
                        stop();
                        break;

                    case 'play':
                        play();
                        break;

                    case 'gotoStart':
                        gotoAndStop('start');
                        break;
                }
            }
        }
    }
}

Swiffy also doesn't seem to like gotoAndStop(0) so I've had to set a frame label of 'start' on the first frame of the animation.

With all this horrible hackery in place, we're able to remove and restart Swiffy animations. The only issue we've found is that detaching and reattaching has caused issues with embedded SVG fonts and we've ended up converting all text to outlines. The above is used like so:

You can call it on the swiffy stage like so:

var stage = new swiffy.Stage(domObject, swiffyObj);
stage.start();

// when reading to remove this element from the DOM do the following:
stage.setFlashVars('mode=gotoStart');

setTimeout(function () {
    // timeout is required to ensure that the enterframe listener has time to run
    stage.hackPause(true);   // paused animation
}, 100);

// you can then remove the containing div from the DOM, but retain it in memory

// after you reattach the div to the DOM, ensuring we've kept hold of our stage variable in memory, you can restart it like this:

stage.hackPause(false);             // resumes javascript requestAnimationFrame
stage.setFlashVars('mode=play');    // resumes flash animation

Hope this helps someone, but I also really hope Google start to expose some kind of JS API or ExternalInterface to the Swiffy runtime to give us more control over what is really a pretty great tool.

Community
  • 1
  • 1
baseten
  • 1,362
  • 1
  • 13
  • 34
0

Try creating a copy of the swiffyobject before passing it to swiffy.Stage(). Swiffy is modifying the object upon instantiation, so with a copy you can simply recreate after destroy()

  • This doesn't work either I'm afraid. It looks to me like the Swiffy runtime internally caches references to the JS compiled AS3 classes. When you try to instantiate a brand new Swiffy object (or a copy of an existing one as you suggest) if they reference these classes Swiffy throws a wobbly... – baseten Aug 23 '13 at 13:30
  • Thanks for reminding me! I've seen it also and the only way I was able to work-around it was by editing the swiffyobject before using it. See here http://stackoverflow.com/questions/17560764/error-with-google-swiffy-calling-runtime-js-multiple-times-in-the-same-page/18253021#18253021 –  Aug 23 '13 at 20:20