Blob Blame History Raw
/*
 * Copyright (C) 2013 Intel Corporation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */

const Lang = imports.lang;
const Mainloop = imports.mainloop;

const Gettext = imports.gettext;
const _ = imports.gettext.gettext;

const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Clutter = imports.gi.Clutter;
const ClutterGst = imports.gi.ClutterGst;

const COLUMNS = 5;
const ROWS = 4;
const TILE_WIDTH = 200;
const TILE_HEIGHT = 200;
const AUDIO_VOLUME_MAX = 0.30;

if (ARGV.length < 2)
    throw "Need at least 2 arguments : video-wall.js videofile1 videofile2 ...";

//

Players.prototype = {
    _init: function() {
        this._current = 1;
        this._currentFilename = 0;

        this._players = [];
        this._players[0] = new ClutterGst.Playback();
        this._players[0].set_audio_volume(0);
        this._players[1] = new ClutterGst.Playback();
        this._players[1].set_audio_volume(0);
        this._players[2] = new ClutterGst.Playback();
        this._players[2].set_audio_volume(0);
    },

    //

    switchFilenamesForward: function() {
        this._currentFilename += 1;
        this._currentFilename %= ARGV.length;
    },

    switchFilenamesBackward: function() {
        this._currentFilename += ARGV.length - 1;
        this._currentFilename %= ARGV.length;
    },

    getCurrentFilename: function() {
        return ARGV[this._currentFilename];
    },

    getNextFilename: function() {
        return ARGV[(this._currentFilename + 1) % ARGV.length];
    },

    getPreviousFilename: function() {
        return ARGV[(this._currentFilename + ARGV.length - 1) % ARGV.length];
    },

    //

    getCurrentPlayer: function() {
        return this._players[this._current];
    },

    getNextPlayer: function() {
        return this._players[(this._current + 1) % this._players.length];
    },

    getPreviousPlayer: function() {
        return this._players[(this._current + this._players.length - 1) %
                             this._players.length];
    },

    switchPlayersForward: function() {
        this.switchFilenamesForward();
        this.getPreviousPlayer().set_playing(false);
        this.getPreviousPlayer().set_filename(this.getNextFilename());
        this.getPreviousPlayer().set_playing(true);
        this.getPreviousPlayer().set_playing(false);
        this.getPreviousPlayer().set_audio_volume(0);
        this._current++;
        this._current %= this._players.length;
    },

    switchPlayersBackward: function() {
        this.switchFilenamesBackward();
        this.getNextPlayer().set_playing(false);
        this.getNextPlayer().set_filename(this.getPreviousFilename());
        this.getNextPlayer().set_playing(true);
        this.getPreviousPlayer().set_playing(false);
        this.getNextPlayer().set_audio_volume(0);
        this._current += this._players.length - 1;
        this._current %= this._players.length;
    },

    //

    updateActorsCurrent: function(actors, back) {
        for (let i in actors) {
            if (back)
                actors[i]._backActor.content.player = this.getCurrentPlayer();
            else
                actors[i].content.player = this.getCurrentPlayer();
        }
    },

    updateActorsNext: function(actors, back) {
        for (let i in actors) {
            if (back)
                actors[i]._backActor.content.player = this.getNextPlayer();
            else
                actors[i].content.player = this.getNextPlayer();
        }
    },

    updateActorsPrevious: function(actors, back) {
        for (let i in actors) {
            if (back)
                actors[i]._backActor.content.player = this.getPreviousPlayer();
            else
                actors[i].content.player = this.getPreviousPlayer();
        }
    },

    dumpFilenames: function() {
        log(this.getPreviousPlayer().uri + "/" + this.getPreviousPlayer().get_playing());
        log(this.getCurrentPlayer().uri + "/" + this.getCurrentPlayer().get_playing());
        log(this.getNextPlayer().uri + "/" + this.getNextPlayer().get_playing());
        log("---------------");
    },
};

function Players() {
    this._init();
}

//

Clutter.init(null, null);
ClutterGst.init(null, null);

let animateActor = function(actor, params) {
    let diffPropName = null;

    actor.save_easing_state();
    if (params.duration)
        actor.set_easing_duration(params.duration);
    if (params.mode)
        actor.set_easing_mode(params.mode);
    if (params.delay)
        actor.set_easing_delay(params.delay);

    for (let p in params.properties) {
        let t = actor.get_transition(p);
        if (t != null && t.is_playing())
            return true;

        if (actor[p] != params.properties[p]) {
            actor[p] = params.properties[p];
            diffPropName = p;
        }
    }

    actor.restore_easing_state();

    if (diffPropName != null && params.onCompleted) {
        let transition = actor.get_transition(diffPropName);
        actor.connect('transition-stopped::' + diffPropName,
                      Lang.bind(params.scope, function() {
                          params.onCompleted(actor);
                      }));
    }

    return (diffPropName != null);
};

//

let stage = new Clutter.Stage({ width: TILE_WIDTH * COLUMNS,
                                height: TILE_HEIGHT * ROWS,
                              });
log(stage.width + "x" + stage.height);
stage.set_background_color(new Clutter.Color({ red: 0,
                                               green: 0,
                                               blue: 0,
                                               alpha: 0xff }));
stage.set_user_resizable(true);
stage.connect('destroy',
              Lang.bind(this, function() { Clutter.main_quit(); }));

let players = new Players();
players.switchPlayersForward();
players.switchPlayersForward();
players.switchPlayersForward();
players.switchPlayersBackward();


let actors = [];

let positionActor = function(actorId) {
    let actor = actors[actorId % actors.length];

    let stageWidth = stage.get_width();
    let stageHeight = stage.get_height();

    actor._backActor.x = actor.x = actor._myXPos * actor.width + (stageWidth / 2) - (COLUMNS * actor.width) / 2;
    actor._backActor.y = actor.y = actor._myYPos * actor.height + (stageHeight / 2) - (ROWS * actor.height) / 2;
};

let positionActors = function() {
    for (let i = 0; i < actors.length; i++)
        positionActor(i);
};

for (let i = 0; i < ROWS; i++) {
    for (let j = 0; j < COLUMNS; j++) {
        let input = new ClutterGst.Box({ x1: j / COLUMNS,
                                         x2: (j + 1) / COLUMNS,
                                         y1: i / ROWS,
                                         y2: (i + 1) / ROWS,
                                       })
        let actor =
            new Clutter.Actor({
                pivot_point: new Clutter.Point({ x: 0.5,
                                                 y: 0.5 }),
                width: TILE_WIDTH,
                height: TILE_HEIGHT,
                content: new ClutterGst.Crop({ cull_backface: true,
                                               input_region: input,
                                             }),
            });
        actor._backActor =
            new Clutter.Actor({
                pivot_point: new Clutter.Point({ x: 0.5,
                                                 y: 0.5 }),
                rotation_angle_y: 180,
                width: TILE_WIDTH,
                height: TILE_HEIGHT,
                content: new ClutterGst.Crop({ cull_backface: true,
                                               input_region: input,
                                             }),
            });
        stage.add_child(actor);
        stage.add_child(actor._backActor);

        actor._myXPos = j;
        actor._myYPos = i;
        actor._backActor._myXPos = j;
        actor._backActor._myYPos = i;

        actors.push(actor);
    }
}

players.updateActorsCurrent(actors, false);
players.getCurrentPlayer().set_audio_volume(AUDIO_VOLUME_MAX);
players.getCurrentPlayer().set_playing(true);

//
//
//

let animateSound = function(duration, mode, newPlayer, oldPlayer) {
    let tl = new Clutter.Timeline({
        duration: duration,
        progress_mode: mode,
    });

    let oldLevel = oldPlayer.get_audio_volume();

    tl.connect('new-frame', Lang.bind(this, function() {
        newPlayer.set_audio_volume(oldLevel * tl.get_progress());
        oldPlayer.set_audio_volume(oldLevel * (1 - tl.get_progress()))
    }));
    tl.start();
};

let animateForward = function(duration, mode, delay) {
    for (let i in actors) {
        let actor = actors[i];
        let actorDelay = delay * (((actor._myXPos  + actor._myYPos * COLUMNS / ROWS) / (2 * COLUMNS)));
        let props = { duration: duration,
                      mode: mode,
                      delay: actorDelay,
                      properties: {
                          'rotation-angle-y': actor.rotation_angle_y + 180,
                      },
                    };

        animateActor(actor, props);
        props.properties['rotation-angle-y'] = actor._backActor.rotation_angle_y + 180;
        animateActor(actor._backActor, props);
    }
};

let animateBackward = function(duration, mode, delay) {
    for (let i in actors) {
        let actor = actors[i];
        let actorDelay = delay * (1 - ((actor._myXPos  + actor._myYPos * COLUMNS / ROWS) / (2 * COLUMNS)));
        let props = { duration: duration,
                      mode: mode,
                      delay: actorDelay,
                      properties: {
                          'rotation-angle-y': actor.rotation_angle_y - 180,
                      },
                    };

        animateActor(actor, props);
        props.properties['rotation-angle-y'] = actor._backActor.rotation_angle_y - 180;
        animateActor(actor._backActor, props);
    }
};

stage.connect('allocation-changed', Lang.bind(this, function() {
    positionActors();
}));

let inAnimation = false;
let nextSide = true;
stage.connect('key-press-event', Lang.bind(this, function(actor, event) {
    if (inAnimation)
        return false;

    log('event processing!');
    let currentPlayer = players.getCurrentPlayer();
    let nextPlayer;

    //players.dumpFilenames();

    switch (event.get_key_symbol()) {
    default:
        return false;

    case Clutter.Up:
        players.updateActorsNext(actors, nextSide);
        animateForward(400, Clutter.AnimationMode.EASE_IN_OUT_CUBIC, 1000);
        nextPlayer = players.getNextPlayer();
        players.switchPlayersForward();
        break;

    case Clutter.Down:
        players.updateActorsPrevious(actors, nextSide);
        animateBackward(400, Clutter.AnimationMode.EASE_IN_OUT_CUBIC, 1000);
        nextPlayer = players.getPreviousPlayer();
        players.switchPlayersBackward();
        break;
    }

    inAnimation = true;
    nextSide = !nextSide;

    animateSound(400 + 1000, Clutter.AnimationMode.EASE_IN_OUT_CUBIC,
                 nextPlayer, currentPlayer);
    nextPlayer.set_playing(true);


    Mainloop.timeout_add(400 + 1000 + 50, Lang.bind(this, function() {
        players.getPreviousPlayer().set_playing(false);
        //players.dumpFilenames();
        inAnimation = false;
        log('event ended');
    }));

    return true;
}));

stage.show();

Clutter.main();