diff --git a/ajax_callbacks.inc b/ajax_callbacks.inc new file mode 100755 index 0000000000000000000000000000000000000000..05d87a7f8839d192536c99697c52f4585f601c59 --- /dev/null +++ b/ajax_callbacks.inc @@ -0,0 +1,113 @@ +<?php + +/** + * @file - AJAX callbacks + */ + +/* + * Ajax function + * If a teacher clicks on the (Un)publish or Delete button in get_overview(), Ajax will handle this request. + */ + +function process_overview_test() { + //object from the class test for accessing his functions + $testObj = new Test(); + $test_info = $_POST['var']; + $checkArray = $test_info[0]; + $action = $test_info[1]; + + if ($action == '(un)publish') { + + foreach ($checkArray as $testid) { + _qtici_publishTest($testid); + } + + drupal_set_message(t('De records werden succesvol (niet) zichtbaar gemaakt.')); + } + elseif ($action == 'delete') { + + foreach ($checkArray as $testid) { + $testObj->deleteTestByTestIDOROlatID($testid); + } + drupal_set_message(t('De records werden succesvol verwijderd.')); + } + exit; +} + +function _qtici_simple_ajax($form, &$form_state) { + return $form['content']; +} + +function _qtici_statsFilter_ajax($form, &$form_state) { + unset($form_state['values']['testid']); + return $form['list']; +} + +/** + * the callback funtion for the auto complete on the exercie overview page + */ +function _qtici_autocomplete($string) { + $matches = array(); + + // Some fantasy DB table which holds cities + $query = db_select('qtici_test', 't'); + + // Select rows that match the string + $return = $query + ->fields('t', array('title')) + ->condition('t.title', '%' . db_like($string) . '%', 'LIKE') + ->range(0, 10) + ->execute(); + + // add matches to $matches + foreach ($return as $row) { + $matches[$row->title] = check_plain($row->title); + } + + // return for JS + drupal_json_output($matches); +} + +/* + * callback funtion for refreshing the table + */ + +function overview_table_callback($form, &$form_state) { + return $form['overview_table']; +} + +function check_answers_ajax_callback($form, $form_state) { + + //get the item id out of the triggering element + if (($tmp = strrchr($form_state['triggering_element']['#name'], '_')) !== false) { + $itemid = substr($tmp, 1); + } + + //higher the attempts done + $_SESSION['exercise']['attempts']['item_' . $itemid] += 1; + + if (isset($form['items'])) { + return $form['items'][$itemid]["content_" . $itemid]; + } + else { + return $form["content_" . $itemid]; + } +} + +function show_answers_ajax_callback($form, $form_state) { + + //get the item id out of the triggering element + if (($tmp = strrchr($form_state['triggering_element']['#name'], '_')) != false) { + $itemid = substr($tmp, 1); + } + + //get the maximum attempts that are allowed + $max_attempts = $form_state['values']['attempts_' . $itemid]; + + if (isset($form['items'])) { + return $form['items'][$itemid]["content_" . $itemid]; + } + else { + return $form["content_" . $itemid]; + } +} diff --git a/classes/ElementTypes.php b/classes/ElementTypes.php new file mode 100755 index 0000000000000000000000000000000000000000..864ad16b82f50251f1693d00bcc26a9cb93b1e45 --- /dev/null +++ b/classes/ElementTypes.php @@ -0,0 +1,17 @@ +<?php +/** + * Description of ElementTypes + * This enumeration is especially used for FIB + * --> determining whether is will be a text element or textbox + * + * @author Joey + */ +class ElementTypes { + const TEXT = 'text'; + const TEXTBOX = 'textbox'; + const RADIOBUTTON = 'radiobutton'; + const CHECKBOX = 'checkbox'; + const IMAGE = 'image'; +} + +?> diff --git a/classes/Feedback.php b/classes/Feedback.php new file mode 100755 index 0000000000000000000000000000000000000000..dcd58407f9fb3da110a68e7e274f9078352e3a96 --- /dev/null +++ b/classes/Feedback.php @@ -0,0 +1,170 @@ +<?php + +class Feedback { + + public $id; + public $itemid; + public $possibilityid; + public $feedback_possibility; + public $feedback_positive; + public $feedback_negative; + public $hint; + public $solution_feedback; + + function __construct() { + + } + + function myConstruct($id, $itemid, $possibilityid, $feedback_possibility, $feedback_positive, $feedback_negative, $hint, $solution_feedback) { + $this->id = $id; + $this->itemid = $itemid; + $this->possibilityid = $possibilityid; + $this->feedback_possibility = $feedback_possibility; + $this->feedback_positive = $feedback_positive; + $this->feedback_negative = $feedback_negative; + $this->hint = $hint; + $this->solution_feedback = $solution_feedback; + } + + public function getId() { + return $this->id; + } + + public function setId($id) { + $this->id = $id; + } + + public function getItemid() { + return $this->itemid; + } + + public function setItemid($itemid) { + $this->itemid = $itemid; + } + + public function getPossibilityid() { + return $this->possibilityid; + } + + public function setPossibilityid($possibilityid) { + $this->possibilityid = $possibilityid; + } + + public function getFeedback_possibility() { + return $this->feedback_possibility; + } + + public function setFeedback_possibility($feedback_possibility) { + $this->feedback_possibility = $feedback_possibility; + } + + public function getFeedback_positive() { + return $this->feedback_positive; + } + + public function setFeedback_positive($feedback_positive) { + $this->feedback_positive = $feedback_positive; + } + + public function getFeedback_negative() { + return $this->feedback_negative; + } + + public function setFeedback_negative($feedback_negative) { + $this->feedback_negative = $feedback_negative; + } + + public function getHint() { + return $this->hint; + } + + public function setHint($hint) { + $this->hint = $hint; + } + + public function getSolution_feedback() { + return $this->solution_feedback; + } + + public function setSolution_feedback($solution_feedback) { + $this->solution_feedback = $solution_feedback; + } + + /** + * Querries of this class + */ + public function deleteFeedbackByItemID($itemID) { + + $possibilities = _qtici_loadPossibilitiesByItemID($itemID); + + foreach ($possibilities as $possibility) { + db_delete('qtici_feedback') + ->condition('itemid', $itemID) + ->condition('possibilityid', $possibility->id) + ->execute(); + } + } + + /** + * get all the feedback for that specific item + */ + function getFeedbackByItemID($itemid) { + $query = db_select('qtici_feedback', 'f'); + $query->fields('f', array('id', 'itemid', 'possibilityid', 'feedback_possibility', 'feedback_positive', 'feedback_negative', 'hint', 'solution_feedback')); + $query->condition('f.itemid', $itemid, '='); + $resultset = $query->execute(); + + return $resultset; + } + + /** + * Functions of this class + */ + function writeFeedback($item, $possibility, $posID, $itemid, &$failFound, &$masteryFound) { + foreach ($item->getFeedback() as $feedback) { + $feedbackPossibility = NULL; + $foundAnything = FALSE; + $feedbackPositive = NULL; + $feedbackNegative = NULL; + $possibilityid = NULL; + + if ($feedback->getIdent() == 'Mastery') { + if ($masteryFound == FALSE) { + $feedbackPositive = $feedback->getFeedback(); + $masteryFound = TRUE; + $foundAnything = TRUE; + } + } + elseif ($feedback->getIdent() == 'Fail') { + if ($failFound == FALSE) { + $feedbackNegative = $feedback->getFeedback(); + $failFound = TRUE; + $foundAnything = TRUE; + } + } + else { // Refers to possibility id + if ($possibility->getIdent() == $feedback->getIdent()) { + $possibilityid = $posID; + $feedbackPossibility = $feedback->getFeedback(); + $foundAnything = TRUE; + } + } + + if ($foundAnything == TRUE) { + db_insert('qtici_feedback') + ->fields(array('itemid' => $itemid, + 'possibilityid' => checkIfExistAndCast($possibilityid), + 'feedback_possibility' => checkIfExistAndCast($feedbackPossibility), + 'feedback_positive' => checkIfExistAndCast($feedbackPositive), + 'feedback_negative' => checkIfExistAndCast($feedbackNegative), + 'hint' => checkIfExistAndCast($item->getHint()), + 'solution_feedback' => checkIfExistAndCast($item->getSolutionFeedback()) + )) + ->execute(); + } + } + } + +} + +?> diff --git a/classes/Item.php b/classes/Item.php new file mode 100755 index 0000000000000000000000000000000000000000..6c8b65c7cbcac33a7e619cf54f5c9701cff58418 --- /dev/null +++ b/classes/Item.php @@ -0,0 +1,218 @@ +<?php + +class Item extends Entity { + + //name variables same as database columns + // protected $ident; + public $type; + public $title; + public $objective; + public $feedback = array(); + public $hint; + public $solutionFeedback; + public $max_attempts; + public $possibilities = array(); + public $question; + public $id; + public $content; + public $sectionid; + public $description; + + public function __construct($values = array()) { + parent::__construct($values, 'item'); + } + + public function myConstruct($ident, $type, $title, $objective = null, $max_attempts = '') { + $this->id = $ident; + $this->type = $type; + $this->title = $title; + //$this->objective = $objective; + $this->max_attempts = $max_attempts; + } + + public function getDescription() { + return $this->description; + } + + public function setDescription($string) { + $description = array( + 'value' => $string, + 'format' => 'full_html', + ); + $this->description = serialize($description); + } + + public function getSectionid() { + return $this->sectionid; + } + + public function setSectionid($sectionid) { + $this->sectionid = $sectionid; + } + + public function setId($id) { + $this->id = $id; + } + + public function getId() { + return $this->id; + } + + public function getType() { + return $this->type; + } + + public function setType($type) { + $this->type = $type; + } + + public function getQuestion() { + return $this->question; + } + + public function setQuestion($question) { + $this->question = $question; + } + + public function setTitle($title) { + $this->title = $title; + } + + public function getTitle() { + return $this->title; + } + + public function setObjective($objective) { + $this->objective = $objective; + } + + public function getObjective() { + return $this->objective; + } + + public function setFeedback($feedback) { + array_push($this->feedback, $feedback); + } + + public function getFeedback() { + return $this->feedback; + } + + public function setHint($hint) { + $this->hint = $hint; + } + + public function getHint() { + return $this->hint; + } + + public function setSolutionFeedback($solutionFeedback) { + $this->solutionFeedback = $solutionFeedback; + } + + public function getSolutionFeedback() { + return $this->solutionFeedback; + } + + public function getMax_attempts() { + return $this->max_attempts; + } + + public function setMax_attempts($max_attempts) { + $this->max_attempts = $max_attempts; + } + + public function setPossibility($possibilities) { + array_push($this->possibilities, $possibilities); + } + + public function getPossibilities() { + return $this->possibilities; + } + + public function deletePossibilities() { + $this->possibilities = array(); + } + + public function setContent($content) { + $this->content = serialize($content); + } + + public function getContent() { + return unserialize($this->content); + } + + /** + * Queries of this class + */ + + public function getQuestionByItem() { + + $query = db_select('qtici_item', 'i'); + $query->fields('i', array('question')); + $query->condition('i.id', $this->id, '='); + $result = $query->execute(); + + return $result; + } + + public function parseXML($item) { + $this->setId((string) getDataIfExists($item, 'attributes()', 'ident')); + $this->setTitle((string) getDataIfExists($item, 'attributes()', 'title')); + $this->setObjective((string) getDataIfExists($item, 'objectives', 'material', 'mattext')); + $this->setDescription((string) getDataIfExists($item, 'objectives', 'material', 'mattext')); + + qtici_fetchFeedback($this, $item); + } + + /** + * Checks if answer (defined per exercise) + */ + public function checkAnswer($form_state) { + $returnArray = array(); + return $returnArray; + } + + /** + * Checks answer to return test score (defined per exercise) + */ + public function checkAnswerForTest($form_state) { + return $this->checkAnswer($form_state); + } + + /** + * Creates exercise form + */ + public function makeExerciseForm(&$info, &$options = array(), &$possibilities = array()) { + + //insert the possibilities and answers for that item in the options array + $options = array(); + $possibilities = _qtici_loadPossibilitiesByItemID($this->id); + + //make the options and answers + foreach ($possibilities as $posibility) { + + //process the blob value + $answerArray = unserialize($posibility->answer); + + //insert al the possible options + $options += array($posibility->id => $answerArray["value"]); + } + + $info = _qtici_checkMedia($this->question, $this->id); + //Make the question/description of the exercise + $itemDescription = ''; + if (!empty($this->description)) { + $dumbVar = unserialize($this->description); + // Don't know if this is necessary since we save the format in the db + $itemDescription = '<div style="margin:10px" class="img_info">' . $dumbVar['value'] . '</div>'; + } + + //label for showing the title and the discription + $form['item_info_' . $this->id] = array( + '#markup' => '<h2 style="margin-bottom: 15px">' . $this->title . '</h2>' . '<span style="padding-left: 5px">' . $itemDescription . '</span>', + ); + + return $form; + } +} diff --git a/classes/Possibility.php b/classes/Possibility.php new file mode 100755 index 0000000000000000000000000000000000000000..ba56d63977d2adc0a62d24522decda562e4d47aa --- /dev/null +++ b/classes/Possibility.php @@ -0,0 +1,127 @@ +<?php + +class Possibility extends Entity { + + public $id; + public $ident; + public $type; + public $possibility; // Type of possibility: radio, checkbox, textbox + public $itemid; + public $answer; // Content of the possibility + public $ordering; + public $is_correct; + public $score; + + public function __construct($values = array()) { + parent::__construct($values, 'possibility'); + } + + function myConstruct($id, $ident, $possibility, $itemid, $answer, $ordering, $is_correct, $score) { + $this->id = $id; + $this->ident = $ident; + $this->possibility = $possibility; + $this->itemid = $itemid; + $this->answer = $answer; + $this->ordering = $ordering; + $this->is_correct = $is_correct; + $this->score = $score; + } + + public function getIdent() { + return $this->ident; + } + + public function setIdent($ident) { + $this->ident = $ident; + } + + public function getId() { + return $this->id; + } + + public function setId($id) { + $this->id = $id; + } + + public function setPossibility($possibility) { + $this->possibility = $possibility; + } + + public function getPossibility() { + return $this->possibility; + } + + public function getItemid() { + return $this->itemid; + } + + public function setItemid($itemid) { + $this->itemid = $itemid; + } + + public function getAnswer() { + return $this->answer; + } + + public function setAnswer($answer) { + $this->answer = $answer; + } + + public function getOrdering() { + return $this->ordering; + } + + public function setOrdering($ordering) { + $this->ordering = $ordering; + } + + public function getIs_correct() { + return $this->is_correct; + } + + public function setIs_correct($is_correct) { + $this->is_correct = $is_correct; + } + + public function getScore() { + return $this->score; + } + + public function setScore($score) { + $this->score = $score; + } + + /** + * Queries of this class + */ + function getAnswerByPossibilityID() { + $query = db_select('qtici_possibility', 'p'); + $query->fields('p', array('id', 'itemid', 'possibility', 'is_correct', 'answer', 'score')); + $query->condition('p.id', $this->id, '='); + $resultset = $query->execute(); + + return $resultset; + } + + function getAnswersByItemID($itemid) { + $query = db_select('qtici_possibility', 'p'); + $query->fields('p', array('id', 'itemid', 'possibility', 'is_correct', 'answer', 'score')); + $query->condition('p.itemid', $itemid, '='); + $resultset = $query->execute(); + + return $resultset; + } + + function getIsCorrectByItemIDANDPossibilityID($itemid) { + $query = db_select('qtici_possibility', 'p'); + $query->fields('p', array('is_correct')); + $query->join('qtici_item', 'i', 'i.id = p.itemid'); + $query->condition('p.itemid', $itemid, '='); + $query->condition('p.id', $this->id, '='); + $result = $query->execute()->fetchAssoc(); + + return $result; + } +} + +?> diff --git a/classes/Section.php b/classes/Section.php new file mode 100755 index 0000000000000000000000000000000000000000..754839f44b1dc5b1104dc5f44f7d7840394f8e24 --- /dev/null +++ b/classes/Section.php @@ -0,0 +1,128 @@ +<?php + +class Section extends Entity { + + //public $ident; + public $id; + public $testid; + public $title; + public $objective; + public $description; + public $ordering; + public $items = array(); + + function __construct() { + + } + + function myConstruct($id) { + $this->id = $id; + } + + function myFullConstruct($section) { + $this->id = $section->id; + $this->testid = $section->testid; + $this->title = $section->title; + $this->description = $section->description; + $this->ordering = $section->ordering; + $this->items = NULL; + } + + public function getId() { + return $this->id; + } + + public function setId($id) { + $this->id = $id; + } + + public function getTestid() { + return $this->testid; + } + + public function setTestid($testid) { + $this->testid = $testid; + } + + public function setTitle($title) { + $this->title = $title; + } + + public function getTitle() { + return $this->title; + } + + public function setObjective($objective) { + $this->objective = $objective; + } + + public function getObjective() { + return $this->objective; + } + + public function getDescription() { + return $this->description; + } + + public function setDescription($description) { + $this->description = $description; + } + + public function setOrdering($ordering) { + $this->ordering = $ordering; + } + + public function getOrdering() { + return $this->ordering; + } + + public function setItem($item) { + array_push($this->items, $item); + } + + public function getItems() { + return $this->items; + } + + /** + * Queries of this class + */ + public function deleteSection() { + if ($this->id) { + db_delete('qtici_section') + ->condition('id', $this->id) + ->execute(); + } + } + + public function getItemsBySection() { + + $query = db_select('qtici_item', 'i'); + $query->fields('i'); + $query->join('qtici_section', 's', 's.id = i.sectionid'); + $query->condition('i.sectionid', $this->id, '='); + $entitys = $query->execute()->fetchAll(PDO::FETCH_CLASS, 'Item'); + + $result = array(); + foreach ($entitys as $entity) { + $result[] = $entity; + } + + return $result; + } + + /** + * Functions of this class + */ + public function makeSectionForm($counter) { + + $form['section_info_' . $counter] = array( + '#markup' => '<div class="expandable img_arrow" style="color:white; background-color:#757575; font-family:Helvetica, Arial, Sans-Serif; font-weight:bold; margin-bottom:15px"> ' . $this->title . '</div> + <div class="divContent"><p>' . $this->description . '</p>', + ); + + return $form; + } +} + +?> diff --git a/classes/Statistic.php b/classes/Statistic.php new file mode 100755 index 0000000000000000000000000000000000000000..6da8a240710a28982ec4b2774bf4dc44e0006c79 --- /dev/null +++ b/classes/Statistic.php @@ -0,0 +1,225 @@ +<?php + +class Statistic { + + private $id; + private $testid; + private $completed; + private $time_spended; + private $score; + private $date_started; + + function __construct() { + } + + function myConstruct($id, $testid, $completed, $time_spended, $score, $date_started) { + $this->id = $id; + $this->testid = $testid; + $this->completed = $completed; + $this->time_spended = $time_spended; + $this->score = $score; + $this->date_started = $date_started; + } + + public function getId() { + return $this->id; + } + + public function setId($id) { + $this->id = $id; + } + + public function getTestid() { + return $this->testid; + } + + public function setTestid($testid) { + $this->testid = $testid; + } + + public function getCompleted() { + return $this->completed; + } + + public function setCompleted($completed) { + $this->completed = $completed; + } + + public function getTime_spended() { + return $this->time_spended; + } + + public function setTime_spended($time_spended) { + $this->time_spended = $time_spended; + } + + public function getScore() { + return $this->score; + } + + public function setScore($score) { + $this->score = $score; + } + + public function getDate_started() { + return $this->date_started; + } + + public function setDate_started($date_started) { + $this->date_started = $date_started; + } + + /** + * Querries of this class + */ + function insertTestStatistics($testid, $completed, $startTime, $score) { + $date = time(); + + $timeSpended = $date - $startTime; + + db_insert('qtici_test_statistics') + ->fields(array( + 'testid' => $testid, + 'completed' => $completed, + 'time_spended' => $timeSpended, + 'score' => $score, + 'date_started' => $date, + )) + ->execute(); + } + + function getAllStatistics($level = '', $topic = '', $tag = NULL) { + // Get all tests + $tests = _qtici_get_exercisesLT($level, $topic, $tag); + $statistic = array(); + // Get general fields + // Total number of tests + $numberTests = count($tests); + // Total number of statistics inizialization + $numberStats = NULL; + // Tests totals + $test_totals = array( + // Total duration + 'duration' => NULL, + // Total number of published tests + 'published' => NULL, + ); + + // Get all statistics + foreach ($tests as $test) { + if ($this->getAllStatisticsByTestID($test['id'])) { + $statistic[] = $this->getAllStatisticsByTestID($test['id']); + $numberStats++; + } + + $testObj = qtici_test_entity_load($test['id']); + + foreach ($test_totals as $key => $value) { + $test_totals[$key] += $testObj->$key; + } + } + + // Tests never done + $neverDone = $numberTests - $numberStats; + + $testObj = new Test(); + + // Total variables + $totals = array( + // Total score + 'score' => NULL, + // Total maximum score + 'max_score' => NULL, + // Total time spent + 'time_spended' => NULL, + // Total number of test completed + 'completed' => NULL, + ); + + // Average variables + $averages = array( + // Average duration + 'duration' => NULL, + // Average spent time + 'time_spended' => NULL, + // Average score + 'score' => NULL, + ); + // Date statistics started + $date_started = REQUEST_TIME; + foreach ($statistic as $stat) { + if ($stat) { + foreach ($totals as $key => $value) { + if ($key !== 'max_score') { + $totals[$key] += $stat[$key]; + } + else { + $totals[$key] = $value + $testObj->getMaximumScoreTest(); + } + } + + foreach ($averages as $key => $value) { + if ($key !== 'score') { + $averages[$key] = $value + ($stat[$key])/$numberStats; + } + else { + $averages[$key] = $value + ($stat[$key])/($numberStats*$testObj->getMaximumScoreTest()); + } + } + + if ($stat['date_started'] < $date_started) { + $date_started = $stat['date_started']; + } + } + } + $test_totals['durationTot'] = $test_totals['duration']; + unset($test_totals['duration']); + $totals['time_spended_tot'] = $totals['time_spended']; + unset($totals['time_spended']); + $totals['totalScore'] = $totals['score']; + unset($totals['score']); + $averages['durationAverage'] = $averages['duration']; + unset($averages['duration']); + $averages['scoreAverage'] = $averages['score']; + unset($averages['score']); + + $statistics = array_merge($test_totals, $totals, $averages, array('numberTests' => $numberTests, 'neverDone' => $neverDone, 'numberStats' => $numberStats, 'date_started' => $date_started)); + + return $statistics; + } + + function getAllStatisticsByTestID($testid) { + $query = db_select('qtici_test_statistics', 'ts'); + $query->fields('ts', array('id', 'testid')); + $query->fields('t', array('id', 'olat_testid', 'title', 'description', 'duration', 'passing_score', 'published', 'answers_in', 'answers_out')); + $query->join('qtici_test', 't', 'ts.testid = t.id'); + $query->addExpression('COUNT(ts.completed)', 'completed'); + $query->addExpression('AVG(ts.time_spended)', 'time_spended'); + $query->addExpression('AVG(ts.score)', 'score'); + $query->addExpression('MAX(ts.date_started)', 'date_started'); + $query->groupBy('t.id'); + $query->havingCondition('t.id', $testid, '='); + $result = $query->execute()->fetchAssoc(); + + return $result; + } + + function checkIfStatisticsExist($testid) { + $query = db_select('qtici_test_statistics', 'ts'); + $query->fields('ts', array('id', 'testid')); + $query->fields('t', array('id')); + $query->join('qtici_test', 't', 'ts.testid = t.id'); + $query->groupBy('t.id'); + $query->havingCondition('t.id', $testid, '='); + $result = $query->execute()->fetchAssoc(); + + if (empty($result)) { + return false; + } + + return true; + } + +} + +?> diff --git a/classes/Test.php b/classes/Test.php new file mode 100755 index 0000000000000000000000000000000000000000..e29d7cdb31c0f21d7504dc01e78a579e75d421fd --- /dev/null +++ b/classes/Test.php @@ -0,0 +1,455 @@ +<?php + +class Test extends Entity { + + public $id; + public $olat_testid; + public $title; + public $description; + public $duration; + public $passing_score; + public $published; + public $answers_in; + public $answers_out; + public $show_answer; + public $check_answer; + public $date; + public $course; + public $topic; + public $level; + public $objective; + public $sections = array(); + + function __construct($values = array()) { + parent::__construct($values, 'test'); + } + + function myConstruct($id, $olat_testid, $title, $description, $duration, $passing_score, $published, $answers_in, $answers_out, $show_answer, $check_answer, $date, $course, $topic, $level, $objective, $sections) { + $this->id = $id; + //Only one bundle for the moment + $this->bundle = 'simple_test'; + $this->olat_testid = $olat_testid; + $this->title = $title; + $this->description = $description; + $this->duration = $duration; + $this->passing_score = $passing_score; + $this->published = $published; + $this->answers_in = $answers_in; + $this->answers_out = $answers_out; + $this->show_answer = $show_answer; + $this->check_answer = $check_answer; + $this->date = $date; + $this->course = $course; + $this->topic = $topic; + $this->level = $level; + $this->objective = $objective; + $this->sections = $sections; + } + + public function setTitle($title) { + $this->title = $title; + } + + public function getTitle() { + return $this->title; + } + + public function setDuration($duration) { + $this->duration = $duration; + } + + public function getDuration() { + return $this->duration; + } + + public function setObjective($objective) { + $this->objective = $objective; + } + + public function getObjective() { + return $this->objective; + } + + public function setSection($sections) { + array_push($this->sections, $sections); + } + + public function getSections() { + return $this->sections; + } + + public function getId() { + return $this->id; + } + + public function setId($id) { + $this->id = $id; + } + + public function getOlat_testid() { + return $this->olat_testid; + } + + public function setOlat_testid($olat_testid) { + $this->olat_testid = $olat_testid; + } + + public function getDescription() { + return $this->description; + } + + public function setDescription($string) { + $description = array( + 'value' => $string, + 'format' => 'full_html', + ); + + $this->description = serialize($description); + } + + public function getPassing_score() { + return $this->passing_score; + } + + public function setPassing_score($passing_score) { + $this->passing_score = $passing_score; + } + + public function getPublished() { + return $this->published; + } + + public function setPublished($published) { + $this->published = $published; + } + + public function getAnswers_in() { + return $this->answers_in; + } + + public function setAnswers_in($answers_in) { + $this->answers_in = $answers_in; + } + + public function getAnswers_out() { + return $this->answers_out; + } + + public function setAnswers_out($answers_out) { + $this->answers_out = $answers_out; + } + + public function getShow_answer() { + return $this->show_answer; + } + + public function setShow_answer($show_answer) { + $this->show_answer = $show_answer; + } + + public function getCheck_answer() { + return $this->check_answer; + } + + public function setCheck_answer($check_answer) { + $this->check_answer = $check_answer; + } + + public function getDate() { + return $this->date; + } + + public function setDate($date) { + $this->date = $date; + } + + public function getCourse() { + return $this->course; + } + + public function setCourse($course) { + $this->course = $course; + } + + public function getTopic() { + return $this->topic; + } + + public function setTopic($topic) { + $this->topic = $topic; + } + + public function getLevel() { + return $this->level; + } + + public function setLevel($level) { + $this->level = $level; + } + + public function setBundle($bundle) { + $this->bundle = $bundle; + } + + /** + * Functions and queries of this class + */ + function getMaximumScoreTest() { + $query = db_select('qtici_possibility', 'p'); + $query->join('qtici_item', 'i', 'p.itemid = i.id'); + $query->join('qtici_section', 's', 'i.sectionid = s.id'); + $query->join('qtici_test', 't', 's.testid = t.id'); + $query->condition('t.id', $this->id, '='); + $query->condition('p.score', 0, '>'); + $query->addExpression('SUM(p.score)'); + $posscore = $query->execute()->fetchAssoc(); + $posscore = (int) $posscore['expression']; + + $query = db_select('qtici_item', 'i'); + $query->join('qtici_section', 's', 'i.sectionid = s.id'); + $query->join('qtici_test', 't', 's.testid = t.id'); + $query->condition('t.id', $this->id, '='); + $query->condition('i.score', 0, '>'); + $query->addExpression('SUM(i.score)'); + $itemscore = $query->execute()->fetchAssoc(); + $itemscore = (int) $itemscore['expression']; + + return $posscore + $itemscore; + } + + function getTestIDByOLATTestID() { + $query = db_select('qtici_test', 't'); + $query->fields('t', array('id')); + $query->condition('t.olat_testid', $this->olat_testid, '='); + $resultset = $query->execute()->fetchAssoc(); + + return $resultset; + } + + public function getAllSectionsByTest() { + + $query = db_select('qtici_section', 's'); + $query->fields('s'); + $query->condition('s.testid', $this->id, '='); + $sections = $query->execute()->fetchAll(PDO::FETCH_CLASS, 'Section'); + + return $sections; + } + + public function getAllSectionIDsByTest() { + + $entitys = $this->getAllSectionsByTest(); + $ids = array(); + + foreach ($entitys as $entity) { + $ids[] = $entity->id; + } + return $ids; + } + + function getPublishedStateOfTest() { + + $test = db_select('qtici_test', 't') + ->fields('t', array('published')) + ->condition('id', $this->id, '=') + ->execute() + ->fetchField(); + + return $test; + } + + function getAllPossibilityIDsFromAllItemsFromAllSectionsInTest() { + + $query = db_select('qtici_possibility', 'p'); + $query->fields('p', array('id', 'itemid')); + $query->join('qtici_item', 'i', 'i.id = p.itemid'); + $query->join('qtici_section', 's', 's.id = i.sectionid'); + $query->condition('s.testid', $this->id, '='); + $possibilities = $query->execute(); + + return $possibilities; + } + + function allPossibilityIDSFromAllItemsFromAllSectionsInTest($testid) { + + $entitys = $this->getAllPossibilityIDsFromAllItemsFromAllSectionsInTest(); + $ids = array(); + + foreach ($entitys as $entity) { + $ids[] = $entity->id; + } + + return $ids; + } + + function deleteStatistic() { + db_delete('qtici_test_statistics') + ->condition('testid', $this->id) + ->execute(); + } + + public function getAllItemsFromAllSectionsInTest() { + + $query = db_select('qtici_item', 'i'); + $query->fields('i', array('id', 'type', 'quotation', 'question', 'score')); + $query->join('qtici_section', 's', 's.id = i.sectionid'); + $query->join('qtici_test', 't', 't.id = s.testid'); + $query->condition('t.id', $this->id, '='); + $entitys = $query->execute(); + + return $entitys; + } + + public function getAllItemIDsFromAllSectionsInTest() { + + $entities = $this->getAllItemsFromAllSectionsInTest(); + $ids = array(); + foreach ($entities as $entity) { + $ids[] = $entity->id; + } + + return $ids; + } + + function deleteTestByTestIDOROlatID($testid = '', $olatTestid = '') { + + if (!empty($olatTestid)) { + $test = $this->getTestIDByOLATTestID(); + $testid = $test['id']; + } + + $possibilities = $this->getAllPossibilityIDsFromAllItemsFromAllSectionsInTest(); + + foreach ($possibilities as $possibility) { + db_delete('qtici_feedback') + ->condition('possibilityid', $possibility->id) + ->execute(); + + db_delete('qtici_possibility') + ->condition('id', $possibility->id) + ->execute(); + } + + $items = $this->getAllItemsFromAllSectionsInTest(); + + foreach ($items as $item) { + db_delete('qtici_item') + ->condition('id', $item->id) + ->execute(); + + db_delete('qtici_feedback') + ->condition('itemid', $item->id) + ->execute(); + } + + $sections = $this->getAllSectionsByTest(); + + foreach ($sections as $section) { + db_delete('qtici_section') + ->condition('id', $section->id) + ->execute(); + } + + + //delete files in /upload/files + $test = qtici_test_entity_load($testid); + $olat_testid = $test['olat_testid']; + $title = $test['title']; + // /var/www/drupal14/index.php + $files = glob(str_replace('index.php', '', $_SERVER['SCRIPT_FILENAME']) . 'sites/default/files/qtici/' . '*', GLOB_MARK); + foreach ($files as $file) { + $dirname = str_replace(str_replace('index.php', '', $_SERVER['SCRIPT_FILENAME']) . 'sites/default/files/qtici/', '', $file); + $dirname = str_replace('/', '', $dirname); + if ($dirname == $olat_testid || $dirname == $title) { + rrmdir($file); + } + } + + db_delete('qtici_test') + ->condition('id', $testid) + ->execute(); + } + + /** + * Implementation of test checking + */ + public function checkTestAnswers($form, $form_state, $itemids) { + $items = qtici_item_entity_load_multiple($itemids); + + $aantaljuist = 0; + $maximum = 0; + + foreach ($items as $id) { + $item = qtici_itemType_entity_load($id->id); + //unset the attempts session + if (isset($_SESSION['exercise']['attempts']['item_' . $item->id])) { + unset($_SESSION['exercise']['attempts']['item_' . $item->id]); + } + + $maximum += $item->score; + $returnArray = $item->checkAnswerForTest($form_state); + + $aantaljuist += $returnArray["score"]; + } + + $return["score"] = $aantaljuist; + $return["max"] = $maximum; + + return $return; + } + + /** + * Saves the categories of a test + */ + public function saveCategories($categories) { + + foreach ($categories as $cat) { + // Check if tag already exists + $terms = taxonomy_get_term_by_name($cat); + if (!empty($terms)) { + // Save already existing tag in taxonomy_entity_index + $query = db_insert('taxonomy_entity_index'); + $query->fields(array('entity_type', 'bundle', 'entity_id', 'revision_id', 'field_name', 'delta', 'tid')); + + foreach ($terms as $term) { + $query->values(array( + 'entity_type' => 'qitic_test', + 'bundle' => 'qtici_test', + 'entity_id' => $this->id, + 'revision_id' => $this->id, + 'field_name' => $term['field_name'], + 'delta' => $term['delta'], + 'tid' => $term['tid'], + )); + } + + $query->execute(); + } + else { + global $vid; + global $field_name; + // Save new tag + $term = new stdClass(); + $term->name = $cat; + $term->vid = $vid; + $term->field_name = $field_name; + taxonomy_term_save($term); + // Save the term in taxonomy_entity_index + $query = db_insert('taxonomy_entity_index'); + $query->fields(array('entity_type', 'bundle', 'entity_id', 'revision_id', 'field_name', 'delta', 'tid')); + $query->values(array( + 'entity_type' => 'qitic_test', + 'bundle' => 'qtici_test', + 'entity_id' => $this->id, + 'revision_id' => $this->id, + 'field_name' => $term['field_name'], + 'delta' => $term['delta'], + 'tid' => $term['tid'], + )); + $query->execute(); + } + } + } +} + +?> diff --git a/classes/exercises/DragAndDropQuestion.php b/classes/exercises/DragAndDropQuestion.php new file mode 100755 index 0000000000000000000000000000000000000000..e00584e48d1678ac982dd5e1eac6ea114d14fbdd --- /dev/null +++ b/classes/exercises/DragAndDropQuestion.php @@ -0,0 +1,229 @@ +<?php + +class DragAndDropQuestion extends Item { + + public $quotation; + public $answers = array(); + public $score; + + public function __construct($values = array()) { + parent::__construct($values, 'qtici_DAD'); + } + + function myFullConstruct($item) { + $this->type = $item->type; + $this->title = $item->title; + $this->objective = NULL; + $this->feedback = NULL; + $this->hint = NULL; + $this->solutionFeedback = NULL; + $this->max_attempts = $item->max_attempts; + $this->possibilities = NULL; + $this->question = $item->question; + $this->id = $item->id; + $this->quotation = $item->quotation; + $this->answers = NULL; + $this->score = $item->score; + $this->quotation = $item->quotation; + } + + public function setQuotation($quotation) { + $this->quotation = $quotation; + } + + public function getQuotation() { + return $this->quotation; + } + + public function setAnswer($answers) { + array_push($this->answers, $answers); + } + + public function getAnswer() { + return $this->answers; + } + + public function setScore($score) { + $this->score = $score; + } + + public function getScore() { + return $this->score; + } + + /** + * Queries of this class + */ + + /** + * Check the answer of an DAD question + */ + public function checkAnswer($form_state) { + + //get the item id + $itemid = $this->getId(); + + //get the ids from the textboxes + $textboxIDS = explode("/", $form_state['values']['texboxenFIBDAD_' . $itemid]); + $correctAnswers = explode("/", trim($form_state['values']['answerTextboxes_' . $itemid])); + + //get rid of the empty values in the array + $textboxIDS = array_filter($textboxIDS); + $correctAnswers = array_filter($correctAnswers); + + //initiatlize string for putting in the vaues of the textboxes + $answerUser = NULL; + $counter = 0; + $score = 0; + + //initialize a color + drupal_add_js(array('qtici' => array('qtici_textbox' => 0, 'qtici_color' => "black")), 'setting'); + + //run through all the ids and get the value of the textbox + foreach ($textboxIDS as $textboxID) { + $answerUser = trim($_POST['textbox_' . $textboxID]); + $correctNum = 0; + + if (isset($correctAnswers[$counter]) && strcmp($answerUser, trim($correctAnswers[$counter])) == 0) { + $correctNum = 1; + } + + if ($correctNum == 1) { + drupal_add_js(array('qtici' => array('qtici_textbox' => $textboxID, 'qtici_color' => "green")), 'setting'); + $score++; + } + else { + drupal_add_js(array('qtici' => array('qtici_textbox' => $textboxID, 'qtici_color' => "red")), 'setting'); + } + + $counter++; + } + + // Call the JS here + drupal_add_js(drupal_get_path('module', 'qtici') . '/js/textboxColorChanger.js'); + + $returnArray["numberOfTextboxes"] = count($textboxIDS); + $returnArray["score"] = $score; + + //look if the answer is correct + if (count($textboxIDS) == $score) { + $returnArray["trueFalse"] = TRUE; + } + else { + $returnArray["trueFalse"] = FALSE; + } + + return $returnArray; + } + + //public function checkAnswerForTest($form, $form_state) { + ////get the item id + //$itemid = $this->getId(); + + ////get the ids from the textboxes + //$textboxIDS = explode("/", $form_state['values']['texboxenFIBDAD_' . $itemid]); + //$correctAnswers = explode("/", trim($form_state['values']['answerTextboxes_' . $itemid])); + + ////get rid of the empty values in the array + //$textboxIDS = array_filter($textboxIDS); + //$correctAnswers = array_filter($correctAnswers); + + ////initiatlize string for putting in the vaues of the textboxes + //$answerUser = NULL; + //$counter = 0; + //$score = 0; + + ////run through all the ids and get the value of the textbox + //foreach ($textboxIDS as $textboxID) { + //$answerUser .= trim($_POST['textbox_' . $textboxID]) . " "; + //$answerArray = explode(' ', trim($_POST['textbox_' . $textboxID])); + //$answerArray = array_filter($answerArray); + //$correctNum = 0; + + //foreach ($answerArray as $word) { + //if (isset($correctAnswers[$counter]) && strcmp($word, trim($correctAnswers[$counter])) == 0) { + //$correctNum++; + //} + //$counter++; + //} + + //if (count($answerArray) != 0 && count($answerArray) == $correctNum) { + //$score++; + //} + //} + + + //$returnArray["numberOfTextboxes"] = count($textboxIDS); + //$returnArray["score"] = $score / count($textboxIDS); + + ////look if the answer is correct + //if (strcmp(trim($form_state['values']['answerTextboxes_' . $itemid]), trim($answerUser)) == 0) { + //$returnArray["trueFalse"] = TRUE; + //} + //else { + //$returnArray["trueFalse"] = FALSE; + //} + + //return $returnArray; + //} + + /** + * Make DAD exercise form + */ + function makeExerciseForm(&$info, &$options = array(), &$possibilities = array()) { + + parent::makeExerciseForm($info, $options, $possibilities); + //initialize variables + $form = array(); + //run through all the possibilities + foreach ($possibilities as $value) { + //initialize the array of possibilitys and give the items a random order + $DADarray[$value->id] = unserialize($value->answer); + } + shuffle($DADarray); + $optionToolbar = '<div class="options">'; + foreach ($DADarray as $key => $value) { + //options toolbar + $optionToolbar .= '<div class="draggable">' . $value['value'] . '</div>'; + } + + $optionToolbar .= '</div><div class="clear"></div>'; + + //$form['options_dd_' . $item->id] = array( + // '#markup' => $optionToolbar, + //); + + $description = db_select('qtici_item', 'i') + ->fields('i', array( + 'description', + )) + ->condition('id', $this->id, '=') + ->execute()->fetchField(); + $description = unserialize($description); + + $form['options_dd_' . $this->id] = array( + '#markup' => '<h2 style="margin-bottom: 15px">' . $this->title . '</h2>' . '<span style="padding-left: 5px">' . $description["value"] . $optionToolbar . '</span>', + ); + + // Get content (field should change name to avoid this) + $content = db_select('qtici_item', 'i') + ->fields('i', array( + 'content', + )) + ->condition('id', $this->id, '=') + ->execute()->fetchField(); + $content = unserialize($content); + //Decode and encode to get rid of weird symbols + $content = htmlentities($content); + $content = html_entity_decode($content, ENT_COMPAT, 'UTF-8'); + $info = _qtici_checkMedia($content, $this->id); + _qtici_checkTextbox($content, TRUE); + $form['question' . $this->id] = array( + '#markup' => $content . "<br /><br />", + ); + + $form['#attached']['library'][] = drupal_add_library('qtici', 'DADQuestion'); + + return $form; + } +} diff --git a/classes/exercises/FillInQuestion.php b/classes/exercises/FillInQuestion.php new file mode 100755 index 0000000000000000000000000000000000000000..450e5de549a9d77f2ed3c373be3a1df526f04419 --- /dev/null +++ b/classes/exercises/FillInQuestion.php @@ -0,0 +1,285 @@ +<?php + +class FillInQuestion extends Item { + + public $quotation; + public $answers = array(); + public $score; + + public function __construct($values = array()) { + parent::__construct($values, 'qtici_FIB'); + } + + function myFullConstruct($item) { + $this->type = $item->type; + $this->title = $item->title; + $this->objective = NULL; + $this->feedback = NULL; + $this->hint = NULL; + $this->solutionFeedback = NULL; + $this->max_attempts = $item->max_attempts; + $this->possibilities = NULL; + $this->question = $item->question; + $this->id = $item->id; + $this->quotation = $item->quotation; + $this->answers = NULL; + $this->score = $item->score; + $this->quotation = $item->quotation; + } + + public function setQuotation($quotation) { + $this->quotation = $quotation; + } + + public function getQuotation() { + return $this->quotation; + } + + public function setAnswer($answers) { + array_push($this->answers, $answers); + } + + public function getAnswer() { + return $this->answers; + } + + public function setScore($score) { + $this->score = $score; + } + + public function getScore() { + return $this->score; + } + + /** + * Functions of this class + */ + + protected function cleanString($string) { + $string = trim($string); + $search = array( "á", "à", "â", "ä", "é", "è", "ê", "í", "ì", "î", "ó", "ò", "ö", "ô", "ú", "ù", "û", "ü" ); + $replace = array( "a", "a", "a", "a", "e", "e", "e", "i", "i", "i", "o", "o", "o", "o", "u", "u", "u", "u"); + $string = preg_replace("/(?![.=$'€%-])\p{P}/u", "", $string); + $string = strtolower($string); + $string = str_replace($search, $replace, $string); + + return $string; + } + + /** + * Check the answer of an FIB question + */ + public function checkAnswer($form_state) { + + //get the item id + $itemid = $this->getId(); + + //get the ids from the textboxes + $textboxIDS = explode("/", $form_state['values']['texboxenFIBDAD_' . $itemid]); + $correctAnswers = explode("/", trim($form_state['values']['answerTextboxes_' . $itemid])); + + //get rid of the empty values in the array + $textboxIDS = array_filter($textboxIDS); + $correctAnswers = array_filter($correctAnswers); + + //initiatlize string for putting in the vaues of the textboxes + $answerUser = NULL; + $counter = 0; + $score = 0; + + //initialize a color + drupal_add_js(array('qtici' => array('qtici_textbox' => 0, 'qtici_color' => "white")), 'setting'); + + //run through all the ids and get the value of the textbox + foreach ($textboxIDS as $textboxID) { + $answerUser = trim($_POST['textbox_' . $textboxID]); + + $correctNum = 0; + + if (isset($correctAnswers[$counter]) && strcmp($this->cleanString($answerUser), $this->cleanString($correctAnswers[$counter])) == 0) { + $correctNum = 1; + } + + if ($correctNum == 1) { + drupal_add_js(array('qtici' => array('qtici_textbox' => $textboxID, 'qtici_color' => "green")), 'setting'); + $score++; + } + else { + drupal_add_js(array('qtici' => array('qtici_textbox' => $textboxID, 'qtici_color' => "red")), 'setting'); + } + + $counter++; + } + + // Call the JS here + drupal_add_js(drupal_get_path('module', 'qtici') . '/js/textboxColorChanger.js'); + + $returnArray["numberOfTextboxes"] = count($textboxIDS); + $returnArray["score"] = $score; + + //look if the answer is correct + if (count($textboxIDS) == $score) { + $returnArray["trueFalse"] = TRUE; + } + else { + $returnArray["trueFalse"] = FALSE; + } + + return $returnArray; + } + + //public function checkAnswerForTest($form, $form_state) { + + //$boxids = $form_state["input"]['texboxenFIBDAD_' . $this->id]; + //$answers = $form_state["input"]['answerTextboxes_' . $this->id]; + ////get the ids from the textboxes + //$textboxIDS = explode(",", $boxids); + //$correctAnswers = explode(" ", trim($answers)); + + ////get rid of the empty values in the array + //$textboxIDS = array_filter($textboxIDS); + //$correctAnswers = array_filter($correctAnswers); + + ////initiatlize string for putting in the vaues of the textboxes + //$answerUser = NULL; + //$counter = 0; + + //$numbercorrect = 0; + + ////run through all the ids and get the value of the textbox + //foreach ($textboxIDS as $textboxID) { + //$answerUser .= trim($_POST['textbox_' . $textboxID]) . " "; + + //if (strcmp(trim($_POST['textbox_' . $textboxID]), trim($correctAnswers[$counter])) == 0) { + //drupal_add_js(array('qtici' => array('qtici_textbox' => $textboxID, 'qtici_color' => "green")), 'setting'); + //$numbercorrect += 1; + //} + //else { + //drupal_add_js(array('qtici' => array('qtici_textbox' => $textboxID, 'qtici_color' => "red")), 'setting'); + //} + + //$counter++; + //} + + //// Call the JS here + //drupal_add_js(drupal_get_path('module', 'qtici') . '/js/textboxColorChanger.js'); + + //$returnArray["numberOfTextboxes"] = $counter; + //$returnarray["score"] = $numbercorrect; + + ////look if the answer is correct + //if (strcmp(trim($answers), trim($answerUser)) == 0) { + //$returnarray["truefalse"] = true; + //} + //else { + //$returnarray["truefalse"] = false; + //} + + //return $returnarray; + //} + + /** + * Make FIB exercise form + */ + function makeExerciseForm(&$info, &$options = array(), &$possibilities = array()) { + + $form = parent::makeExerciseForm($info); + + // Get content (field should change name to avoid this) + $content = db_select('qtici_item', 'i') + ->fields('i', array( + 'content', + )) + ->condition('id', $this->id, '=') + ->execute()->fetchField(); + + $content = unserialize($content); + //Decode and encode to get rid of weird symbols + $content = htmlentities($content); + $content = html_entity_decode($content, ENT_COMPAT, 'UTF-8'); + $info = _qtici_checkMedia($content, $this->id); + _qtici_checkTextbox($content); + + $form['question' . $this->id] = array( + '#markup' => $content . "<br /><br />", + ); + + return $form; + } + + public function parseXML($item) { + // Set Type + $this->setType('FIB'); + // Get Quotation + $outputArray = getQuotationType($item); + $quotation = $outputArray['quotation']; + $results = $outputArray['results']; + + $this->setQuotation($quotation); + + // Get Answers + $answers = array(); + if ($quotation == 'allCorrect') { + foreach ($results as $result) { + foreach ($result->conditionvar->and->or as $arrayAnswer) { + $ident = (int) $arrayAnswer->varequal->attributes()->respident; + $answers[$ident] = (string) $arrayAnswer->varequal; + } + $this->setAnswer($answers); + $this->setScore((string) $result->setvar); + } + } + elseif ($quotation == 'perAnswer') { + foreach ($results as $result) { + $answers[(string) $result->conditionvar->or->varequal['respident']] = array( + 'value' => (string) $result->conditionvar->or->varequal, + 'score' => (string) $result->setvar, + ); + $this->setAnswer($answers); + } + } + + $content = ''; + foreach ($item->presentation->flow->children() as $child) { + // MATERIAL can have the mattext or matimage elements (text/image) + if ($child->getName() == 'material') { + $materialArray = $child->xpath('*'); + foreach ($materialArray as $element) { + if ($element->getName() == 'mattext') { + $content .= (string) $element; + } + if ($element->getName() == 'matimage') { + // Save image + $newFile = file_save_upload((string) $element['uri'], array('file_validate_extensions' => array($allowed))); + $newFile = file_move($newFile, 'public://'); + $newFile->status = 1; // Make permanent + $newFile = file_save($newFile); + $content .= ':img' . $newFile->fid . 'fid:'; + } + } + } + elseif ($child->getName() == 'response_str') { // TEXTBOX + $ident = (int) getDataIfExists($child, 'attributes()', 'ident'); + $content .= ':text' . $ident . 'box:'; + $possibility = new Possibility(); + $answer = NULL; + if ($ident && !empty($answers[$ident])) { + if (is_object($answers[$ident])) { + $answer = $answers[$ident]->value; + } + else { + $answer = $answers[$ident]; + } + } + $dumbAns['value'] = $answer; + $dumbAns['format'] = 'full_html'; + $possibility->myConstruct(NULL, (string) getDataIfExists($child, 'attributes()', 'ident'), ElementTypes::TEXTBOX, NULL, serialize($dumbAns), NULL, NULL, NULL); + $this->setPossibility($possibility); + } + } + $this->setContent(html_entity_decode($content)); + + parent::parseXML($item); + } + +} diff --git a/classes/exercises/KPRIMQuestion.php b/classes/exercises/KPRIMQuestion.php new file mode 100755 index 0000000000000000000000000000000000000000..bc90ac375ae334a3c8d03d34d55c8539476b3d9e --- /dev/null +++ b/classes/exercises/KPRIMQuestion.php @@ -0,0 +1,153 @@ +<?php + +class KPRIMQuestion extends Item { + + public $answers = array(); + public $randomOrder; + public $score; + + public function __construct($values = array()) { + parent::__construct($values, 'qtici_KPRIM'); + } + + public function setAnswer($answers) { + array_push($this->answers, $answers); + } + + public function getAnswer() { + return $this->answers; + } + + public function setRandomOrder($randomOrder) { + if ($randomOrder == 'Yes') { + $this->randomOrder = TRUE; + } + else { + $this->randomOrder = FALSE; + } + } + + public function getRandomOrder() { + return $this->randomOrder; + } + + public function setScore($score) { + $this->score = $score; + } + + public function getScore() { + return $this->score; + } + + /** + * Check the answer of an KRIM question + */ + public function checkAnswer($form_state) { + $itemid = $this->id; + + //get the checked answers + $checkboxAnswers = array_filter($form_state['values']['item_' . $itemid]); + $checkboxAnswersUserText = NULL; + + $returnArray = array(); + + //loop through the given checked answers + foreach ($checkboxAnswers as $checkboxAnswer) { + + //get the answer of the user out of the database + $userAnswer = db_select('qtici_possibility', 'p') + ->fields('p') + ->condition('p.id', $checkboxAnswer) + ->execute() + ->fetchAll(); + + //unserialize the answer + $unserializedAnswer = unserialize($userAnswer[0]->answer); + + //set the test of the checked answer in a string + $checkboxAnswersUserText .= $unserializedAnswer["value"] . " "; + } + + //look if the answer is correct + if (strcmp(trim($form_state['values']['answer_' . $itemid]), trim($checkboxAnswersUserText)) == 0) { + $returnArray['trueFalse'] = TRUE; + } + else { + $returnArray['trueFalse'] = FALSE; + } + + $returnArray['score'] = 0; + $returnArray['numberOfTextboxes'] = 0; + + return $returnArray; + } + + //public function checkAnswerForTest($form, $form_state) { + + //$answers = $form_state["input"]['item_' . $this->id]; + //$correctanswer = $form_state["input"]['answer_' . $this->id]; + ////get the checked answers + //$checkboxAnswers = array_filter($answers); + //$checkboxAnswersUserText = NULL; + + ////loop through the given checked answers + //foreach ($checkboxAnswers as $checkboxAnswer) { + + ////get the answer of the user out of the database + //$userAnswer = db_select('qtici_possibility', 'p') + //->fields('p') + //->condition('p.id', $checkboxAnswer) + //->execute() + //->fetchAll(); + + ////unserialize the answer + //$unserializedAnswer = unserialize($userAnswer[0]->answer); + + ////set the test of the checked answer in a string + //$checkboxAnswersUserText .= $unserializedAnswer["value"] . " "; + //} + + //$returnArray = array(); + + ////look if the answer is correct + //if (strcmp(trim($correctanwser), trim($checkboxAnswersUserText)) == 0) { + //$returnArray['trueFalse'] = TRUE; + //} + //else { + //$returnArray['trueFalse'] = FALSE; + //} + + //$returnArray['score'] = 0; + //$returnArray['numberOfTextboxes'] = 0; + + //return $returnArray; + //} + + public function parseXML($item) { + $this->setRandomOrder((string) getDataIfExists($item, 'presentation', 'response_lid', 'render_choice', 'attributes()', 'shuffle')); + $this->setScore((string) getDataIfExists($item, 'resprocessing', 'outcomes', 'decvar', 'attributes()', 'maxvalue')); + // Set Type + $this->setType('KPRIM'); + // Get answers + foreach ($item->presentation->response_lid->render_choice->children() as $child) { + $possibility = new Possibility(); + $answer['value'] = (string) getDataIfExists($child, 'response_label', 'material', 'mattext'); + $answer['format'] = (string) getDataIfExists($flow_label, 'response_label', 'material', 'mattext', 'texttype'); + $possibility->myConstruct(NULL, (string) getDataIfExists($child, 'response_label', 'attributes()', 'ident'), ElementTypes::RADIOBUTTON, NULL, serialize($answer), NULL, NULL, NULL); + $this->setPossibility($possibility); + } + + $results = $item->xpath('resprocessing/respcondition[conditionvar/and]'); + foreach ($results as $result) { + foreach ($result->conditionvar->and->varequal as $arrayAnswer) { + $id = substr($arrayAnswer, 0, strpos($arrayAnswer, ':')); + $status = substr($arrayAnswer, strpos($arrayAnswer, ':') + 1); + $answers[$id] = $status; + } + } + $this->setAnswer($answers); + + parent::parseXML($item); + } + +} diff --git a/classes/exercises/MarkerQuestion.php b/classes/exercises/MarkerQuestion.php new file mode 100755 index 0000000000000000000000000000000000000000..2469781dbd5e4a8b3e832f6f41f084db286f1e31 --- /dev/null +++ b/classes/exercises/MarkerQuestion.php @@ -0,0 +1,223 @@ +<?php + +class MarkerQuestion extends Item { + + public $answers = array(); + public $score; + + function myFullConstruct($item) { + $this->type = $item->type; + $this->title = $item->title; + $this->objective = NULL; + $this->feedback = NULL; + $this->hint = NULL; + $this->solutionFeedback = NULL; + $this->max_attempts = $item->max_attempts; + $this->possibilities = NULL; + $this->question = $item->question; + $this->id = $item->id; + $this->quotation = $item->quotation; + $this->answers = NULL; + $this->score = $item->score; + } + + public function __construct($values = array()) { + parent::__construct($values, 'qtici_MRK'); + } + + public function getAnswers() { + return $this->answers; + } + + public function setAnswers($answers) { + $this->answers = $answers; + } + + public function getScore() { + return $this->score; + } + + public function setScore($score) { + $this->score = $score; + } + + /** + * Functions of this class + */ + + /** + * Check the answer of an MRK question + */ + public function checkAnswer($form_state) { + $itemid = $this->id; + + //initialize variables + $studentAnswer = NULL; + $databaseAnswer = NULL; + $max_score = 0; + $returnArray = array(); + $returnArray['score'] = 0; + + $returnArray["trueFalse"] = FALSE; + //look if the hidden field with the selected text is filled in + if (isset($form_state['values']['MRK_hidden_' . $itemid])) { + + //set the selectend value in a variable + $studentAnswer = $this->MRK_userAnswers($form_state['values']['MRK_hidden_' . $itemid]); + + //get the array of the correct database answer + $databaseAnswer = $this->MRK_validate(); + $databaseAnswer = array_filter($databaseAnswer); + $max_score = count($databaseAnswer); + + // Compare arrays + $diff = array_diff($studentAnswer, $databaseAnswer); + $returnArray['score'] = $max_score - count($diff); + } + + //look if the user answer is correct + if (isset($diff) && empty($diff)) { + $returnArray["trueFalse"] = TRUE; + } + + $returnArray['numberOfTextboxes'] = 0; + + return $returnArray; + } + + //public function checkAnswerForTest($form, $form_state) { + + //$hiddenvariables = $form_state["input"]['MRK_hidden_' . $this->id]; + ////initialize variables + //$studentAnswer = NULL; + //$databaseAnswer = NULL; + //$max_score = 0; + //$returnArray = array(); + //$returnArray['score'] = 0; + + //$returnArray["trueFalse"] = FALSE; + ////look if the hidden field with the selected text is filled in + //if (isset($hiddenvariables)) { + + ////set the selectend value in a variable + //$studentAnswer = $this->MRK_userAnswers($hiddenvariables); + ////get the array of the correct database answer + //$databaseAnswer = $this->MRK_validate(); + //$databaseAnswer = array_filter($databaseAnswer); + //$max_score = count($databaseAnswer); + + //// Compare arrays + //$diff = array_diff($studentAnswer, $databaseAnswer); + //$returnArray['score'] = $max_score - count($diff); + //} + + ////look if the user answer is correct + //if (isset($diff) && empty($diff)) { + //$returnArray["trueFalse"] = TRUE; + //} + + //$returnArray['numberOfTextboxes'] = 0; + + //return $returnArray; + //} + + /** + * Make MRK exercise form + */ + function makeExerciseForm(&$info, &$options = array(), &$possibilities = array()) { + + $form = parent::makeExerciseForm($info, $options); + + //get the keys of the value for getting the first possibility + $keys = array_keys($options); + + $form['MRK_hidden_' . $this->id] = array( + '#type' => 'hidden', + ); + + $form['itemt_' . $this->id] = array( + '#type' => 'item', + '#required' => FALSE, + '#title' => $this->question, + '#description' => t(''), + '#markup' => + "<style> + .selectable_style .ui-selecting { background: #FECA40; } + .selectable_style .ui-selected { background: #F39814; color: white; } + .selectable_style { width: 80%; margin-left: 50px; } + .selectable_style p {margin-left:20px; } + </style> + <div id=\"selectable_$this->id\">" . filter_xss($options[$keys[0]]) . "</div><span style=\"display:none\" id=\"item_$this->id\"></span><br/>", + ); + + $form['#attached']['library'][] = drupal_add_library('qtici', 'markerQuestion'); + + return $form; + } + + /** + * Get Answers + */ + + function findAnswers() { + $query = db_select('qtici_possibility', 'p') + ->fields('p', array( + 'answer', + )) + ->condition('itemid', $this->id, '=') + ->condition('is_correct', 1, '=') + ->execute(); + + // Markers must come from SCQ exercises, therefore only one result can popup + $answer = $query->fetchField(); + $answer_u = unserialize($answer); + + return $answer_u; + } + + /** + * Return answer string for CheckAnswer + */ + function MRK_validate() { + $answer = $this->findAnswers(); + $result = _qtici_findAnswers($answer['value']); + + return $result; + } + + /** + * Get User answers + */ + function MRK_userAnswers($numbers) { + $answer = $this->findAnswers(); + $string = filter_xss($answer['value']); + $string = strip_tags($string); + $l_pos = explode('-', $numbers); + + $result = array(); + $word = ''; + $last_pos = FALSE; + foreach ($l_pos as $position) { + $letter = substr($string, $position - 1, 1); + if (is_bool($last_pos) || $last_pos + 1 == $position) { + $word .= $letter; + } + else { + $result[] = $word; + $word = $letter; + } + $last_pos = $position; + } + + if ($last_pos == end($l_pos)) { + $result[] = $word; + } + + // Remove white spaces at the beginning or the end of the words + $result_t = array_map('trim', $result); + + return $result_t; + } +} + +?> diff --git a/classes/exercises/MultipleChoiceQuestion.php b/classes/exercises/MultipleChoiceQuestion.php new file mode 100755 index 0000000000000000000000000000000000000000..693b0d5acce0877b48af2c1f54f43750193d036f --- /dev/null +++ b/classes/exercises/MultipleChoiceQuestion.php @@ -0,0 +1,286 @@ +<?php + +class MultipleChoiceQuestion extends Item { + + public $quotation; + public $answers = array(); + public $score; + public $randomOrder; + + public function __construct($values = array()) { + parent::__construct($values, 'qtici_MCQ'); + } + + function myFullConstruct($item) { + $this->type = $item->type; + $this->title = $item->title; + $this->objective = NULL; + $this->feedback = NULL; + $this->hint = NULL; + $this->solutionFeedback = NULL; + $this->max_attempts = $item->max_attempts; + $this->possibilities = NULL; + $this->question = $item->question; + $this->id = $item->id; + $this->quotation = $item->quotation; + $this->answers = NULL; + $this->score = $item->score; + $this->randomOrder = $item->ordering; + } + + public function setQuotation($quotation) { + $this->quotation = $quotation; + } + + public function getQuotation() { + return $this->quotation; + } + + public function setAnswer($answers) { + array_push($this->answers, $answers); + } + + public function getAnswer() { + return $this->answers; + } + + public function setScore($score) { + $this->score = $score; + } + + public function getScore() { + return $this->score; + } + + public function setRandomOrder($randomOrder) { + if ($randomOrder == 'Yes') { + $this->randomOrder = TRUE; + } + else { + $this->randomOrder = FALSE; + } + } + + public function getRandomOrder() { + return $this->randomOrder; + } + + /** + * Functions of this class + */ + + /** + * Check the answer of an MCQ question + */ + public function checkAnswer($form_state) { + + //get the item id + $itemid = $this->getId(); + + $returnArray = array(); + + //get the checked answers + $checkboxAnswers = array_filter($form_state['values']['item_' . $itemid]); + $checkboxAnswersUserText = NULL; + $poss = _qtici_getCorrectPossibilityForItem($itemid); + $userAnswer = array(); + + //loop through the given checked answers + foreach ($checkboxAnswers as $checkboxAnswer) { + + //get the answer of the user out of the database + $userPoss = db_select('qtici_possibility', 'p') + ->fields('p') + ->condition('p.id', $checkboxAnswer) + ->execute() + ->fetchAll(); + + //unserialize the answer + $unserializedAnswer = unserialize($userPoss[0]->answer); + $userAnswer[] = $unserializedAnswer["value"]; + $checkboxAnswersUserText = $unserializedAnswer['value'] . ' '; + } + + $counter = 0; + $score = 0; + $anwersUser = explode(" ", $checkboxAnswersUserText); + $anwersUser = array_filter($anwersUser); + + //get the score of ge user, + if ($poss == $userAnswer) { + $returnArray["trueFalse"] = TRUE; + $returnArray["score"] = $this->score; + } + else { + if (count($poss) >= count($userAnswer)) { + $result = array_diff($poss, $userAnswer); + $score = count(array_diff($poss, $result)) / count($poss); + } + else { + $result = array_diff($userAnswer, $poss); + $score = $this->score - count($result)/ count($poss); + } + $returnArray["trueFalse"] = FALSE; + $returnArray["score"] = $score; + } + + $returnArray['numberOfTextboxes'] = 0; + + return $returnArray; + } + + //public function checkAnswerForTest($form, $form_state) { + + //$answers = $form_state["input"]['item_' . $this->id]; + //$correctanswer = $form_state["input"]['answer_' . $this->id]; + //$itemscore = $this->score; + ////get the checked answers + //$checkboxAnswers = array_filter($answers); + //$checkboxAnswersUserText = NULL; + + //$correctanswerexplosion = explode(" ", $correctanwser); + + //$scoretoadd = count($correctanswerexplosion) / $itemscore; + + //$score = 0; + + //$returnarray = array(); + + ////loop through the given checked answers + //foreach ($checkboxAnswers as $checkboxAnswer) { + + ////get the answer of the user out of the database + //$userAnswer = db_select('qtici_possibility', 'p') + //->fields('p') + //->condition('p.id', $checkboxAnswer) + //->execute() + //->fetchAll(); + + ////unserialize the answer + //$unserializedAnswer = unserialize($userAnswer[0]->answer); + + ////set the test of the checked answer in a string + //$checkboxAnswersUserText .= $unserializedAnswer["value"] . " "; + + //foreach ($correctanswerexplosion as $answer) { + //if ($answer == $unserializedAnswer["value"]) { + //$score += $scoretoadd; + //} + //} + //} + + //$returnarray["score"] = $score; + + ////look if the answer is correct + //if (strcmp(trim($correctanwser), trim($checkboxAnswersUserText)) == 0) { + //$returnarray["truefalse"] = TRUE; + //} + //else { + //$returnarray["truefalse"] = FALSE; + //} + + //$returnArray['numberOfTextboxes'] = 0; + + //return $returnarray; + //} + + /** + * Make multi choice exercise form + */ + function makeExerciseForm(&$info, &$options = array(), &$possibilities = array()) { + + $form = parent::makeExerciseForm($info, $options); + $form['item_question' . $this->id] = array( + '#markup' => htmlspecialchars_decode($this->question), + ); + + $form['item_' . $this->id] = array( + '#type' => 'checkboxes', + '#required' => FALSE, + '#title' => t(''), + '#title_display' => 'invisible', + '#options' => $options, + '#theme' => 'qtici_item_form_checkboxes', + ); + + return $form; + } + + /** + * Parser function. $item is the loaded XML object + */ + public function parseXML($item) { + $this->setRandomOrder((string) getDataIfExists($item, 'presentation', 'response_lid', 'render_choice', 'attributes()', 'shuffle')); + // Set Type + $this->setType('MCQ'); + // Get Quotation + $outputArray = getQuotationType($item); + $quotation = $outputArray['quotation']; + $results = $outputArray['results']; + + $this->setQuotation($quotation); + + // Get correct answers + $correct = array(); + foreach ($item->resprocessing->respcondition as $resp) { + if ($resp->attributes()->title == 'Mastery') { + if (getDataIfExists($resp, 'conditionvar', 'and', 'varequal')) { + foreach ($resp->conditionvar->and->varequal as $varequal) { + $correct[] = (int) getDataIfExists($varequal); + } + } + else { + foreach ($resp->conditionvar->varequal as $varequal) { + $correct[] = (int) getDataIfExists($varequal); + } + } + } + } + + // Get answers + foreach ($item->presentation->response_lid->render_choice->children() as $child) { + $possibility = new Possibility(); + $content['value'] = (string) getDataIfExists($child, 'response_label', 'material', 'mattext'); + $content['format'] = (string) getDataIfExists($child, 'response_label', 'material', 'mattext', 'texttype'); + if (empty($content['format'])) { + $content['format'] = 'full_html'; + } + $ident = (int) getDataIfExists($child, 'response_label', 'attributes()', 'ident'); + $is_correct = 0; + if (in_array($ident, $correct)) { + $is_correct = 1; + } + $possibility->myConstruct(NULL, $ident, ElementTypes::CHECKBOX, NULL, serialize($content), NULL, $is_correct, NULL); + $this->setPossibility($possibility); + } + + // Set Score + $answers = array(); + if ($quotation == 'allCorrect') { + foreach ($results as $result) { + $arrayAnswer = $result->conditionvar->and; + if (count($arrayAnswer->varequal) != 0) { + for ($i = 0; $i < count($arrayAnswer->varequal); $i++) { + $title = (string) $result->attributes()->title; + $answers[$title] = (string) $arrayAnswer->varequal[$i]; + } + } + $this->setScore((string) $results[0]->setvar); + } + } + elseif ($quotation == 'perAnswer') { + foreach ($results as $result) { + $answers[$result->attributes()->title] = array( + 'value' => (string) $result->conditionvar->varequal, + 'score' => (string) $result->setvar, + ); + } + } + $this->setAnswer($answers); + + parent::parseXML($item); + } + +} + +?> diff --git a/classes/exercises/RecorderQuestion.php b/classes/exercises/RecorderQuestion.php new file mode 100755 index 0000000000000000000000000000000000000000..b7cb42cf378a75f3d1c7eae1d1e7758f96022912 --- /dev/null +++ b/classes/exercises/RecorderQuestion.php @@ -0,0 +1,58 @@ +<?php + +class RecorderQuestion extends Item { + + public $score; + + public function __construct($values = array()) { + parent::__construct($values, 'qtici_REC'); + } + + public function setScore($score) { + $this->score = $score; + } + + public function getScore() { + return $this->score; + } + + /* + * Make recorder question exercise form + * The method expects the item for which the form should be made + * and the options of the form + */ + + function makeExerciseForm(&$info, &$options = array(), &$possibilities = array()) { + + $form = parent::makeExerciseForm($info); + + $form['item_' . $this->id] = array( + '#markup' => $this->question . $this->addRecorder(), + ); + + // Disable buttons + $_SESSION['exercise']['show_answer_' . $this->id] = 5; + $_SESSION['exercise']['check_answer_' . $this->id] = 5; + + return $form; + } + + /** + * Add recorder to a page + */ + function addRecorder() { + drupal_add_library('qtici', 'recorder'); + global $base_url; + $html = '<div id="item_rec' . $this->id . '" style="width: 230px; height: 140px"></div>'; + $html .= '<input type="button" value="' . t('Record') . '" id="item_rec_but' . $this->id . '" class="form-submit" />'; + $html .= '<input type="button" value="' . t('Stop') . '" id="item_stop_but' . $this->id . '" class="form-submit" />'; + $html .= '<input type="button" value="' . t('Play') . '" id="item_play_but' . $this->id . '" class="form-submit" />'; + $html .= '<div id="time_' . $this->id . '"></div>'; + $swf = $base_url . '/' . drupal_get_path('module', 'qtici') . '/js/recorder/recorder.swf'; + drupal_add_js(array('recorder' => array('swf' => $swf)), 'setting'); + + drupal_add_js(array('recorder' => array('id' => $this->id)), 'setting'); + drupal_add_library('qtici', 'recorder-conf'); + return $html; + } +} diff --git a/classes/exercises/SingleChoiceQuestion.php b/classes/exercises/SingleChoiceQuestion.php new file mode 100755 index 0000000000000000000000000000000000000000..2f52cefd409a17aede0f6527bc7beac1ce6b35cd --- /dev/null +++ b/classes/exercises/SingleChoiceQuestion.php @@ -0,0 +1,195 @@ +<?php + +class SingleChoiceQuestion extends Item { + + public $answer; + public $score; + public $randomOrder; + + public function __construct($values = array()) { + parent::__construct($values, 'qtici_SCQ'); + } + + function myFullConstruct($item) { + $this->type = $item->type; + $this->title = $item->title; + $this->objective = NULL; + $this->feedback = NULL; + $this->hint = NULL; + $this->solutionFeedback = NULL; + $this->max_attempts = $item->max_attempts; + $this->possibilities = NULL; + $this->question = $item->question; + $this->id = $item->id; + $this->answer = NULL; + $this->score = $item->score; + $this->randomOrder = $item->ordering; + } + + public function setAnswer($answer) { + $this->answer = $answer; + } + + public function getAnswer() { + return $this->answer; + } + + public function setScore($score) { + $this->score = $score; + } + + public function getScore() { + return $this->score; + } + + public function setRandomOrder($randomOrder) { + if ($randomOrder == 'Yes') { + $this->randomOrder = TRUE; + } + else { + $this->randomOrder = FALSE; + } + } + + public function getRandomOrder() { + return $this->randomOrder; + } + + /** + * Functions of this class + */ + + /** + * Check the answer of an SCQ question + */ + public function checkAnswer($form_state) { + + $returnArray = array(); + $userAnswer = $form_state["values"]['item_' . $this->id]; + //get the answer of the user out of the database + $checkCorrect = db_select('qtici_possibility', 'p') + ->fields('p', array('is_correct')) + ->condition('p.id', $userAnswer) + ->execute() + ->fetchField(); + + //look if the answer is correct + if ($checkCorrect == 1) { + $returnArray["trueFalse"] = true; + } + else { + $returnArray["trueFalse"] = FALSE; + } + + $returnArray['score'] = 0; + $returnArray['numberOfTextboxes'] = 0; + + return $returnArray; + } + + //public function checkAnswerForTest($form, $form_state) { + + /*$returnArray = array(); + + $possibility = $form["items"][$this->id]["item_" . $this->id]["#value"]; + + if (!empty($possibility)) { + //get the answer of the user out of the database + $userAnswer = db_select('qtici_possibility', 'p') + ->fields('p') + ->condition('p.id', $possibility) + ->execute() + ->fetchAll(); + + //look if the answer is correct + if ($userAnswer[0]->is_correct == 1) { + $returnArray["trueFalse"] = TRUE; + } + else { + $returnArray["trueFalse"] = FALSE; + } + } + else { + $returnArray["trueFalse"] = FALSE; + } + + $returnArray['score'] = 0; + $returnArray['numberOfTextboxes'] = 0; + + return $returnArray;*/ + + //return $this->checkAnswer($form_state); + //} + + /** + * Make single choice exercise form + */ + function makeExerciseForm(&$info, &$options = array(), &$possibilities = array()) { + + $form = parent::makeExerciseForm($info, $options); + $form['item_question' . $this->id] = array( + '#markup' => htmlspecialchars_decode($this->question), + ); + + $newOps = array(); + foreach ($options as $key => $option) { + $info += _qtici_checkMedia($option, $this->getId()); + $newOps[$key] = $option; + } + + $form['item_' . $this->id] = array( + '#type' => 'radios', + '#required' => FALSE, + '#title' => t('Title'), + '#title_display' => 'invisible', + '#options' => $newOps, + '#theme' => 'qtici_item_form_radios', + ); + + return $form; + } + + /** + * Parser function. $item is the loaded XML object + */ + public function parseXML($item) { + + $this->setMax_attempts((string) getDataIfExists($item, 'attributes()', 'maxattempts')); + $this->setRandomOrder((string) getDataIfExists($item, 'presentation', 'response_lid', 'render_choice', 'attributes()', 'shuffle')); + // Set Type + $this->setType('SCQ'); + // Get correct answers + $correct = array(); + foreach ($item->resprocessing->respcondition as $resp) { + if ($resp->attributes()->title == 'Mastery') { + if (getDataIfExists($resp, 'conditionvar', 'and', 'varequal')) { + $correct[] = (int) getDataIfExists($resp, 'conditionvar', 'and', 'varequal'); + } + else { + $correct[] = (int) getDataIfExists($resp, 'conditionvar', 'varequal'); + } + } + } + // For SCQ we only need to handle answers, the rest of the data is generic to all items + foreach ($item->presentation->response_lid->render_choice->children() as $flow_label) { + $possibility = new Possibility(); + $content['value'] = (string) getDataIfExists($flow_label, 'response_label', 'material', 'mattext'); + $content['format'] = (string) getDataIfExists($flow_label, 'response_label', 'material', 'mattext', 'texttype'); + if (empty($content['format'])) { + $content['format'] = 'full_html'; + } + $ident = (int) getDataIfExists($flow_label, 'response_label', 'attributes()', 'ident'); + $is_correct = 0; + if (in_array($ident, $correct)) { + $is_correct = 1; + } + $possibility->myConstruct(NULL, $ident, ElementTypes::RADIOBUTTON, NULL, serialize($content), NULL, $is_correct, NULL); + $this->setPossibility($possibility); + } + + parent::parseXML($item); + } + +} + +?> diff --git a/classes/exercises/VideoQuestion.php b/classes/exercises/VideoQuestion.php new file mode 100755 index 0000000000000000000000000000000000000000..0b8c85040dad03111df6e9b08a22388526b2d95f --- /dev/null +++ b/classes/exercises/VideoQuestion.php @@ -0,0 +1,204 @@ +<?php + +class VideoQuestion extends Item { + + public $answers = array(); + public $score; + + public function __construct($values = array()) { + parent::__construct($values, 'qtici_VID'); + } + + public function getAnswers() { + return $this->answers; + } + + public function setAnswers($answers) { + $this->answers = $answers; + } + + public function getScore() { + return $this->score; + } + + public function setScore($score) { + $this->score = $score; + } + + /** + * Check the answer of a VID question + */ + public function checkAnswer($form_state) { + + $returnArray = array(); + $spendedTime = $form_state["values"]["VID_hidden_" . $this->id]; + $answer_array = $this->VID_validate(); + $studentAnswerArray = explode('-', $spendedTime); + $studentAnswerArray = array_filter($studentAnswerArray); + + $returnArray["trueFalse"] = true; + $returnArray['numberOfTextboxes'] = count($answer_array); + $returnArray["score"] = 1; + $scorePiece = 1 / count($answer_array); + + for ($i = 0; $i <= count($answer_array) - 1; $i++) { + + if (isset($studentAnswerArray[$i])) { + $simpleSpendedTime = floor($studentAnswerArray[$i]); + + $answer_item = explode('-', $answer_array[$i]); + $begin_time = explode(',', $answer_item[0]); + $end_time = explode(',', $answer_item[1]); + $answer_item[0] = $begin_time[0] * 60 + $begin_time[1]; + $answer_item[1] = $end_time[0] * 60 + $end_time[1]; + + if ($simpleSpendedTime < $answer_item[0] || $simpleSpendedTime > $answer_item[1]) { + $returnArray["trueFalse"] = FALSE; + $returnArray["score"] = $returnArray["score"] - $scorePiece; + } + } + else { + $returnArray["score"] = $returnArray["score"] - $scorePiece; + $returnArray["trueFalse"] = FALSE; + } + } + + return $returnArray; + } + + + + //public function checkAnswerForTest($form, $form_state) { + + //$returnArray = array(); + //$spendedTime = $form_state["input"]['VID_hidden_' . $this->id]; + //$answer_array = $this->VID_validate(); + //$studentAnswerArray = explode('-', $spendedTime); + //$studentAnswerArray = array_filter($studentAnswerArray); + + //$returnArray["trueFalse"] = true; + //$returnArray['numberOfTextboxes'] = count($answer_array); + //$returnArray["score"] = 1; + //$scorePiece = 1 / count($answer_array); + + //for ($i = 0; $i <= count($answer_array) - 1; $i++) { + + //if (isset($studentAnswerArray[$i])) { + //$simpleSpendedTime = floor($studentAnswerArray[$i]); + + //$answer_item = explode('-', $answer_array[$i]); + //$begin_time = explode(',', $answer_item[0]); + //$end_time = explode(',', $answer_item[1]); + //$answer_item[0] = $begin_time[0] * 60 + $begin_time[1]; + //$answer_item[1] = $end_time[0] * 60 + $end_time[1]; + + //if ($simpleSpendedTime < $answer_item[0] || $simpleSpendedTime > $answer_item[1]) { + //$returnArray["trueFalse"] = FALSE; + //$returnArray["score"] = $returnArray["score"] - $scorePiece; + //} + //} + //else { + //$returnArray["score"] = $returnArray["score"] - $scorePiece; + //$returnArray["trueFalse"] = FALSE; + //} + //} + + //return $returnArray; + //} + + /** + * Used by checkAnswer + */ + private function VID_validate() { + $result = _qtici_getCorrectPossibilityForItem($this->id); + + foreach ($result as $item) { + $answer[] = str_replace(' ', '', $item); + } + + return $answer; + } + + /** + * Display function for video exercises + */ + public function makeExerciseForm(&$info, &$options = array(), &$possibilities = array()) { + + $form = parent::makeExerciseForm($info, $options, $possibilities); + + //make the hidden field that holds the times for checking later + $form['VID_hidden_' . $this->id] = array( + '#type' => 'hidden', + '#attributes' => array('id' => "VID_hidden_" . $this->id), + ); + + //make the question + $form['VID_' . $this->id] = array( + '#type' => 'item', + '#title' => htmlspecialchars_decode($this->question), + ); + + //make the button for getting the current time + $form["VID_getTime_" . $this->id] = array( + '#markup' => '<input type="button" value="' . t('Grijp tijd') . '" id = "get_time_player_' . $this->id . '" name = "grip_button" class = "form-submit">', + ); + //get the table for displaying the input boxes for the times + $table = $this->create_videoTimesTable(count($possibilities) - 1); + + //display the table + $form['VID_table' . $this->id] = array( + '#type' => 'item', + '#markup' => $table, + ); + + $form['#attached']['library'][] = drupal_add_library('qtici', 'videoQuestion'); + + return $form; + } + + /** + * make the table for the video exercise for displaying the input fields for the selected times + */ + private function create_videoTimesTable($possibilitiesCount) { + + //make the input fields + $inputFields = NULL; + for ($i = 0; $i <= $possibilitiesCount; $i++) { + + //make one input field + $inputField = array( + '#type' => 'textfield', + '#title' => t(''), + '#description' => t(''), + '#size' => 6, + '#attributes' => array( + 'readonly' => 'readonly', + 'id' => 'inputbox_time_player_' . $this->id . '_id_' . $i, + 'style' => 'background-color:#CFF; float: left;', + 'class' => array('time_stamp_player'), + 'name' => 'inputbox_time_player_' . $this->id . '_id_' . $i, + ), + ); + + //render the input field and add it to the others + $inputFields .= render($inputField); + } + + //make the rows of the table + $options[] = array( + 'header1' => $inputFields, + 'header2' => '<input type="button" value="' . t('Opnieuw') . '" name = "qclear_button" class = "qtici_clear_button_' . $this->id . '">', + ); + + //render the table + $html = theme('table', array( + 'rows' => $options, + 'sticky' => TRUE, //Optional to indicate whether the table headers should be sticky + ) + ); + + return $html; + } +} + +?> diff --git a/css/block_exercises.css b/css/block_exercises.css new file mode 100755 index 0000000000000000000000000000000000000000..1e9f1bb9de02ea28d89a6a89d60226261daddf76 --- /dev/null +++ b/css/block_exercises.css @@ -0,0 +1,37 @@ +.block_exercises { + margin-top: 3px; + margin-left: -10px; + padding: 0px; + font-size: 12px; + list-style: none; +} + +.block_exercises li { + background: none; +} + +.block_exercises li a { + text-decoration: none; + color: #3f6069; +} + +.block_exercises li a:hover { + color: #ce1432; +} + +.block_exercises li ul { + list-style: none; + display: none; + margin-left: -10px; + font-size: 11px; + width: 130px; +} + +.block_exercises li ul li a { + color: #e9860c; +} + +/** Avoid breaking the audio class **/ +.mejs-inner { + height: inherit !important; +} diff --git a/css/css.css b/css/css.css new file mode 100755 index 0000000000000000000000000000000000000000..f173567c1996c43fdd3e1931716d84e9b05bdead --- /dev/null +++ b/css/css.css @@ -0,0 +1,1311 @@ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * General page setup + */ +#dt_example { + font: 80%/1.45em "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; + margin: 0; + padding: 0; + color: #333; + background-color: #fff; +} + + +#dt_example #container { + width: 800px; + margin: 30px auto; + padding: 0; +} + + +#dt_example #footer { + margin: 50px auto 0 auto; + padding: 0; +} + +#dt_example #demo { + margin: 30px auto 0 auto; +} + +#dt_example .demo_jui { + margin: 30px auto 0 auto; +} + +#dt_example .big { + font-size: 1.3em; + font-weight: bold; + line-height: 1.6em; + color: #4E6CA3; +} + +#dt_example .spacer { + height: 20px; + clear: both; +} + +#dt_example .clear { + clear: both; +} + +#dt_example pre { + padding: 15px; + background-color: #F5F5F5; + border: 1px solid #CCCCCC; +} + +#dt_example h1 { + margin-top: 2em; + font-size: 1.3em; + font-weight: normal; + line-height: 1.6em; + color: #4E6CA3; + border-bottom: 1px solid #B0BED9; + clear: both; +} + +#dt_example h2 { + font-size: 1.2em; + font-weight: normal; + line-height: 1.6em; + color: #4E6CA3; + clear: both; +} + +#dt_example a { + color: #0063DC; + text-decoration: none; +} + +#dt_example a:hover { + text-decoration: underline; +} + +#dt_example ul { + color: #4E6CA3; +} + +.css_right { + float: right; +} + +.css_left { + float: left; +} + +.demo_links { + float: left; + width: 50%; + margin-bottom: 1em; +} + +#demo_info { + padding: 5px; + border: 1px solid #B0BED9; + height: 100px; + width: 100%; + overflow: auto; +} +/* + * File: demo_table_jui.css + * CVS: $Id$ + * Description: CSS descriptions for DataTables demo pages + * Author: Allan Jardine + * Created: Tue May 12 06:47:22 BST 2009 + * Modified: $Date$ by $Author$ + * Language: CSS + * Project: DataTables + * + * Copyright 2009 Allan Jardine. All Rights Reserved. + * + * *************************************************************************** + * DESCRIPTION + * + * The styles given here are suitable for the demos that are used with the standard DataTables + * distribution (see www.datatables.net). You will most likely wish to modify these styles to + * meet the layout requirements of your site. + * + * Common issues: + * 'full_numbers' pagination - I use an extra selector on the body tag to ensure that there is + * no conflict between the two pagination types. If you want to use full_numbers pagination + * ensure that you either have "example_alt_pagination" as a body class name, or better yet, + * modify that selector. + * Note that the path used for Images is relative. All images are by default located in + * ../images/ - relative to this CSS file. + */ + + +/* + * jQuery UI specific styling + */ + +.paging_two_button .ui-button { + float: left; + cursor: pointer; + * cursor: hand; +} + +.paging_full_numbers .ui-button { + padding: 2px 6px; + margin: 0; + cursor: pointer; + * cursor: hand; + color: #333 !important; +} + +.dataTables_paginate .ui-button { + margin-right: -0.1em !important; +} + +.paging_full_numbers { + width: 350px !important; +} + +.dataTables_wrapper .ui-toolbar { + padding: 5px; +} + +.dataTables_paginate { + width: auto; +} + +.dataTables_info { + padding-top: 3px; +} + +table.display thead th { + padding: 3px 0px 3px 10px; + cursor: pointer; + * cursor: hand; +} + +div.dataTables_wrapper .ui-widget-header { + font-weight: normal; +} + + +/* + * Sort arrow icon positioning + */ +table.display thead th div.DataTables_sort_wrapper { + position: relative; + padding-right: 20px; + padding-right: 20px; +} + +table.display thead th div.DataTables_sort_wrapper span { + position: absolute; + top: 50%; + margin-top: -8px; + right: 0; +} + + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Everything below this line is the same as demo_table.css. This file is + * required for 'cleanliness' of the markup + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables features + */ + +.dataTables_wrapper { + position: relative; + clear: both; +} + +.dataTables_processing { + position: absolute; + top: 0px; + left: 50%; + width: 250px; + margin-left: -125px; + border: 1px solid #ddd; + text-align: center; + color: #999; + font-size: 11px; + padding: 2px 0; +} + +.dataTables_length { + width: 40%; + float: left; +} + +.dataTables_filter { + width: 50%; + float: right; + text-align: right; +} + +.dataTables_filterDrupal { + text-align: right; +} + +#edit-search { + height:15px; +} + +.dataTables_info { + width: 50%; + float: left; +} + +.dataTables_paginate { + float: right; + text-align: right; +} + +/* Pagination nested */ +.paginate_disabled_previous, .paginate_enabled_previous, .paginate_disabled_next, .paginate_enabled_next { + height: 19px; + width: 19px; + margin-left: 3px; + float: left; +} + +.paginate_disabled_previous { + background-image: url('images/back_disabled.jpg'); +} + +.paginate_enabled_previous { + background-image: url('images/back_enabled.jpg'); +} + +.paginate_disabled_next { + background-image: url('images/forward_disabled.jpg'); +} + +.paginate_enabled_next { + background-image: url('images/forward_enabled.jpg'); +} + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables display + */ +table.display { + margin: 0 auto; + width: 100%; + clear: both; + border-collapse: collapse; +} + +table.display tfoot th { + padding: 3px 0px 3px 10px; + font-weight: bold; + font-weight: normal; +} + +table.display tr.heading2 td { + border-bottom: 1px solid #aaa; +} + +table.display td { + padding: 3px 10px; +} + +table.display td.center { + text-align: center; +} + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables sorting + */ + +.sorting_asc { + background: url('images/sort_asc.png') no-repeat center right; +} + +.sorting_desc { + background: url('images/sort_desc.png') no-repeat center right; +} + +.sorting { + background: url('images/sort_both.png') no-repeat center right; +} + +.sorting_asc_disabled { + background: url('images/sort_asc_disabled.png') no-repeat center right; +} + +.sorting_desc_disabled { + background: url('images/sort_desc_disabled.png') no-repeat center right; +} + + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables row classes + */ +table.display tr.odd.gradeA { + background-color: #ddffdd; +} + +table.display tr.even.gradeA { + background-color: #eeffee; +} + + + + +table.display tr.odd.gradeA { + background-color: #ddffdd; +} + +table.display tr.even.gradeA { + background-color: #eeffee; +} + +table.display tr.odd.gradeC { + background-color: #ddddff; +} + +table.display tr.even.gradeC { + background-color: #eeeeff; +} + +table.display tr.odd.gradeX { + background-color: #ffdddd; +} + +table.display tr.even.gradeX { + background-color: #ffeeee; +} + +table.display tr.odd.gradeU { + background-color: #ddd; +} + +table.display tr.even.gradeU { + background-color: #eee; +} + + +tr.odd { + background-color: #E2E4FF; +} + +tr.even { + background-color: white; +} + + + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Misc + */ +.dataTables_scroll { + clear: both; +} + +.dataTables_scrollBody { + -webkit-overflow-scrolling: touch; +} + +.top, .bottom { + padding: 15px; + background-color: #F5F5F5; + border: 1px solid #CCCCCC; +} + +.top .dataTables_info { + float: none; +} + +.clear { + clear: both; +} + +.dataTables_empty { + text-align: center; +} + +tfoot input { + margin: 0.5em 0; + width: 100%; + color: #444; +} + +tfoot input.search_init { + color: #999; +} + +td.group { + background-color: #d1cfd0; + border-bottom: 2px solid #A19B9E; + border-top: 2px solid #A19B9E; +} + +td.details { + background-color: #d1cfd0; + border: 2px solid #A19B9E; +} + + +.example_alt_pagination div.dataTables_info { + width: 40%; +} + +.paging_full_numbers a.paginate_button, + .paging_full_numbers a.paginate_active { + border: 1px solid #aaa; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + padding: 2px 5px; + margin: 0 3px; + cursor: pointer; + *cursor: hand; + color: #333 !important; +} + +.paging_full_numbers a.paginate_button { + background-color: #ddd; +} + +.paging_full_numbers a.paginate_button:hover { + background-color: #ccc; + text-decoration: none !important; +} + +.paging_full_numbers a.paginate_active { + background-color: #99B3FF; +} + +table.display tr.even.row_selected td { + background-color: #B0BED9; +} + +table.display tr.odd.row_selected td { + background-color: #9FAFD1; +} + + +/* + * Sorting classes for columns + */ +/* For the standard odd/even */ +tr.odd td.sorting_1 { + background-color: #D3D6FF; +} + +tr.odd td.sorting_2 { + background-color: #DADCFF; +} + +tr.odd td.sorting_3 { + background-color: #E0E2FF; +} + +tr.even td.sorting_1 { + background-color: #EAEBFF; +} + +tr.even td.sorting_2 { + background-color: #F2F3FF; +} + +tr.even td.sorting_3 { + background-color: #F9F9FF; +} + + +/* For the Conditional-CSS grading rows */ +/* + Colour calculations (based off the main row colours) + Level 1: + dd > c4 + ee > d5 + Level 2: + dd > d1 + ee > e2 + */ +tr.odd.gradeA td.sorting_1 { + background-color: #c4ffc4; +} + +tr.odd.gradeA td.sorting_2 { + background-color: #d1ffd1; +} + +tr.odd.gradeA td.sorting_3 { + background-color: #d1ffd1; +} + +tr.even.gradeA td.sorting_1 { + background-color: #d5ffd5; +} + +tr.even.gradeA td.sorting_2 { + background-color: #e2ffe2; +} + +tr.even.gradeA td.sorting_3 { + background-color: #e2ffe2; +} + +tr.odd.gradeC td.sorting_1 { + background-color: #c4c4ff; +} + +tr.odd.gradeC td.sorting_2 { + background-color: #d1d1ff; +} + +tr.odd.gradeC td.sorting_3 { + background-color: #d1d1ff; +} + +tr.even.gradeC td.sorting_1 { + background-color: #d5d5ff; +} + +tr.even.gradeC td.sorting_2 { + background-color: #e2e2ff; +} + +tr.even.gradeC td.sorting_3 { + background-color: #e2e2ff; +} + +tr.odd.gradeX td.sorting_1 { + background-color: #ffc4c4; +} + +tr.odd.gradeX td.sorting_2 { + background-color: #ffd1d1; +} + +tr.odd.gradeX td.sorting_3 { + background-color: #ffd1d1; +} + +tr.even.gradeX td.sorting_1 { + background-color: #ffd5d5; +} + +tr.even.gradeX td.sorting_2 { + background-color: #ffe2e2; +} + +tr.even.gradeX td.sorting_3 { + background-color: #ffe2e2; +} + +tr.odd.gradeU td.sorting_1 { + background-color: #c4c4c4; +} + +tr.odd.gradeU td.sorting_2 { + background-color: #d1d1d1; +} + +tr.odd.gradeU td.sorting_3 { + background-color: #d1d1d1; +} + +tr.even.gradeU td.sorting_1 { + background-color: #d5d5d5; +} + +tr.even.gradeU td.sorting_2 { + background-color: #e2e2e2; +} + +tr.even.gradeU td.sorting_3 { + background-color: #e2e2e2; +} + + +/* + * Row highlighting example + */ +.ex_highlight #example tbody tr.even:hover, #example tbody tr.even td.highlighted { + background-color: #ECFFB3; +} + +.ex_highlight #example tbody tr.odd:hover, #example tbody tr.odd td.highlighted { + background-color: #E6FF99; +} +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } +.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; } +.ui-widget-header a { color: #222222; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; } +.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; } +.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* + * jQuery UI Resizable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* + * jQuery UI Selectable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Accordion @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; }/* + * jQuery UI Autocomplete @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* + * jQuery UI Button @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +/* + * jQuery UI Dialog @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* + * jQuery UI Slider @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* + * jQuery UI Tabs @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* + * jQuery UI Datepicker @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* + * jQuery UI Progressbar @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } + + + + + + + +/* +* +*CSS VAN CONF +* +*/ + +/* TABLE + * ========================================================================= */ + table#dnd-treeCourse { + border: 1px solid #888; + border-collapse: collapse; + line-height: 1; + margin: 1em auto; + width: 90%; +} + +/* Caption + * ------------------------------------------------------------------------- */ +table caption { + font-size: .9em; + font-weight: bold; +} + +/* Header + * ------------------------------------------------------------------------- */ +table#dnd-treeCourse thead { + background: #aaa url(images/bg-table-thead.png) repeat-x top left; + font-size: .9em; +} + +table#dnd-treeCourse thead tr th { + border: 1px solid #888; + font-weight: normal; + padding: .3em 1.67em .1em 1.67em; + text-align: left; +} + +/* Body + * ------------------------------------------------------------------------- */ +table#dnd-treeCourse tbody tr td { + cursor: default; + padding: .3em 1.5em; +} + +table#dnd-treeCourse tbody tr.even { + background: #f3f3f3; +} + +table#dnd-treeCourse tbody tr.odd { + background: #fff; +} + +table#dnd-treeCourse span { + background-position: center left; + background-repeat: no-repeat; + padding: .2em 0 .2em 1.5em; +} + +table#dnd-treeCourse span.file { + background-image: url(images/page_white_text.png); +} + +table#dnd-treeCourse span.folder { + background-image: url(images/folder.png); +} + +.treeTable tr td .expander { + cursor: pointer; + padding: 0; + zoom: 1; /* IE7 Hack */ +} + +.treeTable tr td a.expander { + background-position: left center; + background-repeat: no-repeat; + color: #000; + text-decoration: none; +} + +.treeTable tr.collapsed td a.expander { + background-image: url(images/toggle-expand-dark.png); +} + +.treeTable tr.expanded td a.expander { + background-image: url(images/toggle-collapse-dark.png); +} + + +/* Layout helper taken from jQuery UI. This way I don't have to require the + * full jQuery UI CSS to be loaded. */ +.ui-helper-hidden { display: none; } diff --git a/css/images.css b/css/images.css new file mode 100755 index 0000000000000000000000000000000000000000..8c18807d26625a3774c645b97eb27dcdc7a42ffb --- /dev/null +++ b/css/images.css @@ -0,0 +1,97 @@ +/* + Document : images + Created on : 21-mei-2012, 12:54:56 + Author : Joey + Description: + Relative paths to images +*/ + +.img_published { + background: url(images/published.png) no-repeat 0 0; + background-size: 16px 16px; + width: 16px; + height: 16px; + display: inline-block; +} + +.img_unpublished { + background: url(images/unpublished.png) no-repeat 0 0; + background-size: 16px 16px; + width: 16px; + height: 16px; + display: inline-block; +} + +.img_halfpublished { + background: url(images/halfpublished.png) no-repeat 0 0; + background-size: 16px 16px; + width: 16px; + height: 16px; + display: inline-block; +} + +.img_display { + background: url(images/detail16.png) no-repeat 0 0; + background-size: 16px 16px; + width: 16px; + height: 16px; + display: inline-block; +} + +.img_statistics { + background: url(images/statistics.png) no-repeat 0 0; + background-size: 16px 16px; + width: 16px; + height: 16px; + display: inline-block; +} + +.img_nostatistics { + background: url(images/nostatistics.png) no-repeat 0 0; + background-size: 16px 16px; + width: 16px; + height: 16px; + display: inline-block; + +} + +.img_past { + background: url(images/past.png) no-repeat 0 0; + background-size: 16px 16px; + width: 16px; + height: 16px; + display: inline-block; +} + +.img_arrow { + background: url(images/pijl.png) no-repeat 0 0; + background-size: 16px 16px; + background-position-x: center; + background-attachment: fixed; + text-align: left; + padding-top: 5px; + height: 30px; +} + +.img_info { + background: url(images/info.png) no-repeat 0 0; + padding-left: 20px; + display: block; +} + +.img_delete { + background: url(images/delete.png) no-repeat 0 0; + background-size: 16px 16px; + width: 16px; + height: 16px; + display: inline-block; +} + +.img_loading { + background: url(images/loading.gif) no-repeat 0 0; + background-size: 35px 35px; + height: 35px; + display: inline-block; + padding-left: 45px; + line-height: 35px; +} \ No newline at end of file diff --git a/css/images/Sorting icons.psd b/css/images/Sorting icons.psd new file mode 100755 index 0000000000000000000000000000000000000000..53b2e06850767cb57c52b316f0b845b1a8e0ca0e Binary files /dev/null and b/css/images/Sorting icons.psd differ diff --git a/css/images/back_disabled.png b/css/images/back_disabled.png new file mode 100755 index 0000000000000000000000000000000000000000..881de7976ff98955e2a5487dca66e618a0655f3d Binary files /dev/null and b/css/images/back_disabled.png differ diff --git a/css/images/back_enabled.png b/css/images/back_enabled.png new file mode 100755 index 0000000000000000000000000000000000000000..c608682b04a6d9b8002602450c8ef7e80ebba099 Binary files /dev/null and b/css/images/back_enabled.png differ diff --git a/css/images/back_enabled_hover.png b/css/images/back_enabled_hover.png new file mode 100755 index 0000000000000000000000000000000000000000..d300f1064b3beac1d7d5274e294494d3143e53a2 Binary files /dev/null and b/css/images/back_enabled_hover.png differ diff --git a/css/images/bg-table-thead.png b/css/images/bg-table-thead.png new file mode 100755 index 0000000000000000000000000000000000000000..ed58ab51c704942e11680574fb967fe38bed9939 Binary files /dev/null and b/css/images/bg-table-thead.png differ diff --git a/css/images/delete.png b/css/images/delete.png new file mode 100755 index 0000000000000000000000000000000000000000..62c181d4b03e00c588482ae24f7c57479282cd59 Binary files /dev/null and b/css/images/delete.png differ diff --git a/css/images/detail16.png b/css/images/detail16.png new file mode 100755 index 0000000000000000000000000000000000000000..7ff964f83e9bac4d371dc369ab1d075ababc8256 Binary files /dev/null and b/css/images/detail16.png differ diff --git a/css/images/favicon.ico b/css/images/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..6eeaa2a0d393190ce748107222d9a026f992e4a7 Binary files /dev/null and b/css/images/favicon.ico differ diff --git a/css/images/folder.png b/css/images/folder.png new file mode 100755 index 0000000000000000000000000000000000000000..784e8fa48234f4f64b6922a6758f254ee0ca08ec Binary files /dev/null and b/css/images/folder.png differ diff --git a/css/images/forward_disabled.png b/css/images/forward_disabled.png new file mode 100755 index 0000000000000000000000000000000000000000..6a6ded7de821619aedc71d1738c0b73463a4452e Binary files /dev/null and b/css/images/forward_disabled.png differ diff --git a/css/images/forward_enabled.png b/css/images/forward_enabled.png new file mode 100755 index 0000000000000000000000000000000000000000..a4e6b5384b8454ee7f44a8f7c75b0321b7eeb9b1 Binary files /dev/null and b/css/images/forward_enabled.png differ diff --git a/css/images/forward_enabled_hover.png b/css/images/forward_enabled_hover.png new file mode 100755 index 0000000000000000000000000000000000000000..fc46c5ebf0524b72a509fe2d7c1bc74995cb8a9d Binary files /dev/null and b/css/images/forward_enabled_hover.png differ diff --git a/css/images/halfpublished.png b/css/images/halfpublished.png new file mode 100755 index 0000000000000000000000000000000000000000..1ecbad39c60766f4576786ba9df043baffa16512 Binary files /dev/null and b/css/images/halfpublished.png differ diff --git a/css/images/info.png b/css/images/info.png new file mode 100755 index 0000000000000000000000000000000000000000..4f00bfc6614b33b3c3356b857fd8487c19e825b1 Binary files /dev/null and b/css/images/info.png differ diff --git a/css/images/loading.gif b/css/images/loading.gif new file mode 100755 index 0000000000000000000000000000000000000000..53edda20f0c933344d5af825d6128ba6ae4911b8 Binary files /dev/null and b/css/images/loading.gif differ diff --git a/css/images/nostatistics.png b/css/images/nostatistics.png new file mode 100755 index 0000000000000000000000000000000000000000..9d8ceea9224a6d133326fdcfceec845cbc0e06b1 Binary files /dev/null and b/css/images/nostatistics.png differ diff --git a/css/images/page_white_text.png b/css/images/page_white_text.png new file mode 100755 index 0000000000000000000000000000000000000000..813f712f726c935f9adf8d2f2dd0d7683791ef11 Binary files /dev/null and b/css/images/page_white_text.png differ diff --git a/css/images/past.png b/css/images/past.png new file mode 100755 index 0000000000000000000000000000000000000000..da1ee442fbc2101713c9818656f360d6a24436eb Binary files /dev/null and b/css/images/past.png differ diff --git a/css/images/pijl.png b/css/images/pijl.png new file mode 100755 index 0000000000000000000000000000000000000000..95c9983c7edc87b55d4090e29c08c3f111d4f955 Binary files /dev/null and b/css/images/pijl.png differ diff --git a/css/images/published.png b/css/images/published.png new file mode 100755 index 0000000000000000000000000000000000000000..f3a4796b4bfb526ed253f4d5ba06cea43c35debc Binary files /dev/null and b/css/images/published.png differ diff --git a/css/images/sort_asc.png b/css/images/sort_asc.png new file mode 100755 index 0000000000000000000000000000000000000000..a88d7975fe9017e4e5f2289a94bd1ed66a5f59dc Binary files /dev/null and b/css/images/sort_asc.png differ diff --git a/css/images/sort_asc_disabled.png b/css/images/sort_asc_disabled.png new file mode 100755 index 0000000000000000000000000000000000000000..4e144cf0b1f786a9248a2998311e8109998d8a2d Binary files /dev/null and b/css/images/sort_asc_disabled.png differ diff --git a/css/images/sort_both.png b/css/images/sort_both.png new file mode 100755 index 0000000000000000000000000000000000000000..18670406bc01ab2721781822dd6478917745ff54 Binary files /dev/null and b/css/images/sort_both.png differ diff --git a/css/images/sort_desc.png b/css/images/sort_desc.png new file mode 100755 index 0000000000000000000000000000000000000000..def071ed5afd264a036f6d9e75856366fd6ad153 Binary files /dev/null and b/css/images/sort_desc.png differ diff --git a/css/images/sort_desc_disabled.png b/css/images/sort_desc_disabled.png new file mode 100755 index 0000000000000000000000000000000000000000..7824973cc60fc1841b16f2cb39323cefcdc3f942 Binary files /dev/null and b/css/images/sort_desc_disabled.png differ diff --git a/css/images/statistics.png b/css/images/statistics.png new file mode 100755 index 0000000000000000000000000000000000000000..59c4544d8dc60c05ca516bac11a4491e6536844d Binary files /dev/null and b/css/images/statistics.png differ diff --git a/css/images/toggle-collapse-dark.png b/css/images/toggle-collapse-dark.png new file mode 100755 index 0000000000000000000000000000000000000000..76577a57a23105b4c821b851902cda145e348d9c Binary files /dev/null and b/css/images/toggle-collapse-dark.png differ diff --git a/css/images/toggle-expand-dark.png b/css/images/toggle-expand-dark.png new file mode 100755 index 0000000000000000000000000000000000000000..cfb42a451247095be68232ab38f75842893ce407 Binary files /dev/null and b/css/images/toggle-expand-dark.png differ diff --git a/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/css/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..5b5dab2ab7b1c50dea9cfe73dc5a269a92d2d4b4 Binary files /dev/null and b/css/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/css/images/ui-bg_flat_75_ffffff_40x100.png b/css/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..ac8b229af950c29356abf64a6c4aa894575445f0 Binary files /dev/null and b/css/images/ui-bg_flat_75_ffffff_40x100.png differ diff --git a/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/css/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..ad3d6346e00f246102f72f2e026ed0491988b394 Binary files /dev/null and b/css/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/css/images/ui-bg_glass_65_ffffff_1x400.png b/css/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..42ccba269b6e91bef12ad0fa18be651b5ef0ee68 Binary files /dev/null and b/css/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/css/images/ui-bg_glass_75_dadada_1x400.png b/css/images/ui-bg_glass_75_dadada_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..5a46b47cb16631068aee9e0bd61269fc4e95e5cd Binary files /dev/null and b/css/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/css/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..86c2baa655eac8539db34f8d9adb69ec1226201c Binary files /dev/null and b/css/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/css/images/ui-bg_glass_95_fef1ec_1x400.png b/css/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..4443fdc1a156babad4336f004eaf5ca5dfa0f9ab Binary files /dev/null and b/css/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100755 index 0000000000000000000000000000000000000000..7c9fa6c6edcfcdd3e5b77e6f547b719e6fc66e30 Binary files /dev/null and b/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/css/images/ui-icons_222222_256x240.png b/css/images/ui-icons_222222_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..ee039dc096a38a3753f92519546eee94bcfbeffa Binary files /dev/null and b/css/images/ui-icons_222222_256x240.png differ diff --git a/css/images/ui-icons_2e83ff_256x240.png b/css/images/ui-icons_2e83ff_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..45e8928e5284adacea3f9ec07b9b50667d2ac65f Binary files /dev/null and b/css/images/ui-icons_2e83ff_256x240.png differ diff --git a/css/images/ui-icons_454545_256x240.png b/css/images/ui-icons_454545_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..7ec70d11bfb2f77374dfd00ef61ba0c3647b5a0c Binary files /dev/null and b/css/images/ui-icons_454545_256x240.png differ diff --git a/css/images/ui-icons_888888_256x240.png b/css/images/ui-icons_888888_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..5ba708c39172a69e069136bd1309c4322c61f571 Binary files /dev/null and b/css/images/ui-icons_888888_256x240.png differ diff --git a/css/images/ui-icons_cd0a0a_256x240.png b/css/images/ui-icons_cd0a0a_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..7930a558099bc8d92b4264eb67a0f040460f4a4f Binary files /dev/null and b/css/images/ui-icons_cd0a0a_256x240.png differ diff --git a/css/images/unpublished.png b/css/images/unpublished.png new file mode 100755 index 0000000000000000000000000000000000000000..5a92bc5b72fbff47df9f951cf220bb2d79ccfb14 Binary files /dev/null and b/css/images/unpublished.png differ diff --git a/css/images/update16.png b/css/images/update16.png new file mode 100755 index 0000000000000000000000000000000000000000..20ec92ea5151d83313026ba7d312fae816d4d2c4 Binary files /dev/null and b/css/images/update16.png differ diff --git a/forms.inc b/forms.inc new file mode 100755 index 0000000000000000000000000000000000000000..98c7f50a438cdfe5c219988bd315d8d5aa740619 --- /dev/null +++ b/forms.inc @@ -0,0 +1,924 @@ +<?php + +/** + * @file - All forms go in this file + */ +/* + * In this function the sessions are cleared. The sessions are created and used in qtici_pagePreview() & qtici_pagePreview_submit(). + * The reason why they are cleared here is because Drupal executes Callback function twice (no idea why). + */ + +function qtici_pageOverview_form($form, &$form_state) { + if (isset($_SESSION['form_state'])) { + unset($_SESSION['form_state']); + } + if (isset($_SESSION['testid'])) { + unset($_SESSION['testid']); + } + if (isset($_SESSION['totalScore'])) { + unset($_SESSION['totalScore']); + } + + //initialize the variables needed + $form = array(); + drupal_add_css(drupal_get_path('module', 'qtici') . '/css/css.css'); + $default = 0; + $title = ''; + + //look if a value is selected for the row count and if not give it a default + if (empty($form_state['values']['entries'])) { + $default = 10; + } + else { + $default = $form_state['values']['entries']; + } + + //look if a value is given to te title that you want to search for and if not make it blank + if (empty($form_state['values']['search'])) { + $title = ''; + } + else { + $title = $form_state['values']['search']; + } + + //make a dropdown form for choosing the row count + $form['entries'] = array( + '#name' => 'entries', + '#type' => 'select', + '#title' => t('Aantal rijen per pagina: '), + '#default_value' => $default, + '#options' => array(10 => 10, 25 => 25, 50 => 50, 100 => 100), + '#description' => t(''), + '#ajax' => array( + 'callback' => 'overview_table_callback', + 'wrapper' => 'table_excercises', + 'method' => 'replace', + 'effect' => 'fade', + ), + ); + + //Make a search form for searching for titles + $form['search'] = array( + '#type' => 'textfield', + '#title' => t('Zoeken:'), + '#description' => t('Druk Enter om te zoeken'), + '#size' => 35, + '#prefix' => '<div class="dataTables_filterDrupal">', + '#suffix' => '</div>', + '#autocomplete_path' => 'oefeningen/overview/autocomplete', + '#ajax' => array( + 'callback' => 'overview_table_callback', + 'wrapper' => 'table_excercises', + 'method' => 'replace', + 'effect' => 'fade', + 'progress' => array(), + ), + ); + + //look if the user is in the overlay + if (module_exists('overlay') && path_is_admin($_GET['q'])) { + //make an url for ajax call so that it also works the second time + $_GET['q'] = "admin/test/manage"; + } + else { + //make an url for ajax call so that it also works the second time + $_GET['q'] = "oefeningen/overview"; + } + + if (module_exists('mobile_theme')) { + //get the table and the pager you want to show + $array = get_exercise_overview_mobile($default, $title); + } + else { + //get the table and the pager you want to show + $array = get_exercise_overview($default, $title); + } + + //look if the logged in user is a teacher + if (user_access('teacher')) { + //make the table + $form['overview_table'] = array( + '#type' => 'tableselect', + '#header' => $array['header'], + '#options' => $array['options'], + '#empty' => t('No content available.'), + '#prefix' => '<div id="table_excercises">', + '#suffix' => $array['html'] . '</div>', + ); + } + else { + $form['overview_table'] = array( + '#prefix' => '<div id="table_excercises">', + '#suffix' => $array['html'] . '</div>', + '#theme' => 'table', + '#header' => $array['header'], + '#rows' => $array['options'], + '#empty' => t('No content available.'), + ); + } + + //look if the logged in user is a teacher + if (user_access('teacher')) { + //delete button + $form['overview_table_delete'] = array( + '#type' => 'button', + '#value' => t('Delete'), + '#executes_submit_callback' => TRUE, + '#submit' => array('_qtici_deleteTest_submit'), + '#attributes' => array('onclick' => 'if(!confirm("' . t("Wilt u de test echt verwijderen?") . '")){return false;}'), + ); + + //publish unpublish button + $form['overview_table_publish'] = array( + '#type' => 'button', + '#value' => t('(Un)publish'), + '#executes_submit_callback' => TRUE, + '#submit' => array('_qtici_publishTest_submit'), + ); + } + + return $form; +} + +function qtici_item_entity_form($form, &$form_state, $entity) { + //get the possibilities for the entity + $possibilities = _qtici_loadPossibilitiesByItemID($entity->id); + + //get the settings of the test + $testoptions = qtici_test_entity_load($_SESSION['entity_test']['testid']); + + //insert the possibilities and answers for that item in the options array + $options = array(); + $answers = null; + $possibilitiesCount = 0; + + //make the options and answers + foreach ($possibilities as $posibility) { + + //higher the possibilities count + $possibilitiesCount++; + + //process the blob value + $answerArray = unserialize($posibility->getAnswer()); + + //fill in the answer + if ($posibility->is_correct == 1) { + $answers .= $answerArray["value"] . " "; + } + + //insert al the possible options + $options += array($posibility->id => $answerArray["value"]); + } + + if (empty($_SESSION['exercise']['show_answer_' . $entity->id])) { + $_SESSION['exercise']['show_answer_' . $entity->id] = 0; + } + + if (empty($_SESSION['exercise']['check_answer_' . $entity->id])) { + $_SESSION['exercise']['check_answer_' . $entity->id] = 0; + } + + if (empty($_SESSION['exercise']['attempts']['item_' . $entity->id])) { + $_SESSION['exercise']['attempts']['item_' . $entity->id] = 1; + } + + //get the answers of the textboxes + $answerTextboxesText = NULL; + foreach ($possibilities as $answer) { + if (!empty($answer->answer)) { + + //process the blob value + $answerArray = unserialize($answer->answer); + if ($entity->type == "FIB") { + if (is_array($answerArray["value"])) { + $answerArray = $answerArray["value"]; + } + } + $answerTextboxesText .= $answerArray['value'] . "/"; + } + } + + //check if the logged in user has the good rights + global $user; + $check = array_intersect(array('teacher', 'administrator'), array_values($user->roles)); + if (empty($check) ? FALSE : TRUE) { + //give the div for the contextual menu + $form["contextual_menu_begin_" . $entity->id] = array( + '#markup' => "<div class=\"contextual-links-region\">" . theme('contextual', array('destination' => 'item/' . $entity->id, + 'links' => array(array('admin/item/' . $entity->id . '/edit', t('Oefening bewerken'))))) + ); + } + + //attach js to form + $form['#attached']['library'] = array( + drupal_add_library('system', 'ui.draggable'), + drupal_add_library('system', 'ui.droppable'), + drupal_add_library('system', 'ui.selectable'), + drupal_add_library('system', 'ui.position'), + ); + + //get the form for the item + $info = array(); + + $form += $entity->makeExerciseForm($info); + foreach ($info as $fid => $values) { + $audio = FALSE; + if ($values['type'] == 'audio') { + $audio = TRUE; + } + $file = file_load($fid); + $url = file_create_url($file->uri); + _qtici_addFlowPlayer($values['class'] . $fid, $url, $audio); + } + + //initialize a variable that holds the possibility ids with a komma in between for putting it in a hidden + //textbox so you know which textboxes and how manny you need to check later + $textboxen = ""; + foreach ($possibilities as $key) { + $textboxen .= $key->id . '/'; + } + + //holds the textboxes that were generated, soo you can call them in chack answers + $form['texboxenFIBDAD_' . $entity->id] = array( + '#type' => 'hidden', + '#title' => '', + '#default_value' => $textboxen, + '#required' => FALSE, + ); + + //add css for displaying the textboxes next to each other + drupal_add_css('.options > div { border: solid black 1px; padding: 3px; + margin: 5px; color: black; background-color: #61BCED; display: inline-block; z-index: 5} .clear { clear: left; } .item { margin-bottom: 20px; }', $option['type'] = 'inline'); + drupal_add_css('div.container-inline { display: inline; }', $option['type'] = 'inline'); + drupal_add_css('div.container-inline .form-item { display: inline; }', $option['type'] = 'inline'); + + //the answer in a hidden field for getting it in the callback of show_answers + $form['answer_' . $entity->id] = array( + '#type' => 'hidden', + '#title' => '', + '#default_value' => $answers, + '#required' => FALSE, + ); + + //set the count of the possibilities + $form['possibilitiesCount_' . $entity->id] = array( + '#type' => 'hidden', + '#attributes' => array('id' => "possibilitiesCount_" . $entity->id), + '#title' => '', + '#default_value' => $possibilitiesCount - 1, + '#required' => FALSE, + ); + + //the type of the item + $form['type_' . $entity->id] = array( + '#type' => 'hidden', + '#title' => '', + '#default_value' => $entity->type, + '#required' => FALSE, + ); + + //the max attempts of the item + $form['attempts_' . $entity->id] = array( + '#type' => 'hidden', + '#title' => '', + '#default_value' => $entity->max_attempts, + '#required' => FALSE, + ); + + //the quotation of the item + $form['quotation_' . $entity->id] = array( + '#type' => 'hidden', + '#title' => '', + '#default_value' => $entity->quotation, + '#required' => FALSE, + ); + + //the score of the item + $form['score_' . $entity->id] = array( + '#type' => 'hidden', + '#title' => '', + '#default_value' => $entity->score, + '#required' => FALSE, + ); + + //the textbox answers in a hidden field for getting it in the callback of show_answers + $form['answerTextboxes_' . $entity->id] = array( + '#type' => 'hidden', + '#title' => '', + '#default_value' => $answerTextboxesText, + '#required' => FALSE, + ); + + //make a container for the answer label + $form["content_" . $entity->id] = array( + '#type' => 'container', + '#prefix' => '<div id="box_' . $entity->id . '">', + '#suffix' => '</div>', + ); + + $form["content_" . $entity->id]['labelAnswers_' . $entity->id] = array(); + + //change the visibility of the answer variable + $visibility = NULl; + if (!empty($form_state['triggering_element']) && $form_state['triggering_element']['#name'] == 'show_answers_' . $entity->id) { + + if (isset($_SESSION['qtici']['visibility_' . $entity->id])) { + $visibility = $_SESSION['qtici']['visibility_' . $entity->id]; + } + + if ($visibility === 0) { + $visibility = 1; + } + else { + $visibility = 0; + } + } + + //look if the button shculd be displayed + if ($_SESSION['exercise']['check_answer_' . $entity->id] != 5 && ($testoptions->check_answer == NULL || $testoptions->check_answer == 1)) { + //check_answer button + $form["content_" . $entity->id]['check_answer_' . $entity->id] = array( + '#type' => 'button', + '#name' => 'check_answer_' . $entity->id, + '#ajax' => array( + 'callback' => 'check_answers_ajax_callback', + 'wrapper' => 'box_' . $entity->id, + ), + '#value' => t('Controleer mijn antwoord.'), + ); + } + + if (!empty($form_state['triggering_element']) && $form_state['triggering_element']['#name'] == 'check_answer_' . $entity->id) { + //$item = qtici_itemType_entity_load($entity->id); + $result = $entity->checkAnswer($form_state); + $form["content_" . $entity->id]['labelCheck_' . $entity->id] = array( + '#type' => 'item', + '#title' => qtici_makeLabelFeedback($form_state, $entity->id, $result), + ); + + if ($_SESSION['exercise']['attempts']['item_' . $entity->id] != 1 && $_SESSION['exercise']['attempts']['item_' . $entity->id] != NULL && !empty($entity->max_attempts) && $_SESSION['exercise']['attempts']['item_' . $entity->id] >= $entity->max_attempts) { + $form["content_" . $entity->id]['check_answer_' . $entity->id] = array(); + } + } + + //look if the button should be displayed + if ($_SESSION['exercise']['show_answer_' . $entity->id] != 5 && ($testoptions->show_answer == NULL || $testoptions->show_answer == 1)) { + //show_answers button + $form["content_" . $entity->id]['show_answers_' . $entity->id] = array( + '#type' => 'button', + '#name' => 'show_answers_' . $entity->id, + '#ajax' => array( + 'callback' => 'show_answers_ajax_callback', + 'wrapper' => 'box_' . $entity->id, + ), + '#value' => t('Laat het juiste antwoord zien.'), + ); + } + + if (!empty($form_state['triggering_element']) && $form_state['triggering_element']['#name'] == 'show_answers_' . $entity->id && $visibility == 0) { + // display answer + //display a label with answers + if ($entity->type == 'FIB' || $entity->type == 'DAD') { + $form["content_" . $entity->id]['labelAnswers_' . $entity->id] = array( + '#markup' => $answerTextboxesText, + ); + } + else { + $form["content_" . $entity->id]['labelAnswers_' . $entity->id] = array( + '#markup' => $answers, + ); + } + + $form["content_" . $entity->id]['show_answers_' . $entity->id] = array( + '#type' => 'button', + '#name' => 'show_answers_' . $entity->id, + '#ajax' => array( + 'callback' => 'show_answers_ajax_callback', + 'wrapper' => 'box_' . $entity->id, + ), + '#value' => t('Verberg het juiste antwoord.'), + ); + } + + $_SESSION['qtici']['visibility_' . $entity->id] = $visibility; + + $check = array_intersect(array('teacher', 'administrator'), array_values($user->roles)); + if (empty($check) ? FALSE : TRUE) { + //end of the contextual links + $form['contextual_menu_ends_' . $entity->id] = array( + "#markup" => '</div>', + ); + } + + return $form; +} + +function qtici_test_entity_form($form, &$form_state, $entity) { + + drupal_add_js(array('qtici' => array('level' => $entity->level, 'topic' => $entity->topic, 'testId' => $entity->id, 'test' => TRUE)), 'setting'); + + $itemids = $entity->getAllItemIDsFromAllSectionsInTest(); + + $lijst = array(); + + foreach ($itemids as $id) { + $item = qtici_itemType_entity_load($id); + $addform = qtici_item_entity_form($form, $form_state, $item); + $lijst[$item->id] = $addform; + } + + foreach ($lijst as $item_form) { + $form += $item_form; + } + + $form['tags'] = array( + '#markup' => _qtici_get_tag_list($entity), + ); + + variable_set("itemids", $itemids); + + $form["timestarted"] = array( + "#type" => "hidden", + "#value" => time(), + ); + + /*$form["submit"] = array( + '#type' => 'submit', + '#value' => t('Bereken mijn score.'), + //'#execute_submit_callback' => TRUE, + '#weight' => 100, + );*/ + + return $form; +} + +function qtici_test_entity_form_submit($form, &$form_state) { + + $itemids = variable_get("itemids"); + $test = new Test(); + + $answerreturn = $test->checkTestAnswers($form, $form_state, $itemids); + + $statistic = new Statistic(); + $statistic->insertTestStatistics($form_state["build_info"]["args"][0]->id, 1, $form_state["values"]["timestarted"], $answerreturn["score"]); + + //drupal_set_message(print_r($form_state["build_info"]["args"][0]->id, true)); + + drupal_set_message("Mijn score is: " . $answerreturn["score"] . " op " . $answerreturn["max"] . "!"); +} + +/* + * Function to display a table with all the statistics + */ + +function qtici_pageStatistics_form($form, &$form_state) { + + $form['one_test'] = array( + '#type' => 'item', + //'#title' => t(), + '#markup' => t('To see individual statistics of each test, please go to the <a href="@url">test list</a> and select the corresponding button.', array('@url' => url('admin/test/manage'))), + ); + + $form['filters'] = array( + '#type' => 'fieldset', + '#title' => t('Filter Tests'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ); + + global $_qtici_levels; + $form['filters']['level'] = array( + '#type' => 'select', + '#title' => t('Level'), + '#options' => array('NO_LEVEL' => t('-- Select --')) + $_qtici_levels, + '#default_value' => '', + '#ajax' => array( + 'callback' => '_qtici_simple_ajax', + 'wrapper' => 'qtici_statistics_table', + 'method' => 'replace', + 'effect' => 'fade', + ), + ); + + global $_qtici_topics; + $form['filters']['topic'] = array( + '#type' => 'select', + '#title' => t('Topic'), + '#options' => array('NO_TOPIC' => t('-- Select --')) + $_qtici_topics, + '#default_value' => '', + '#ajax' => array( + 'callback' => '_qtici_simple_ajax', + 'wrapper' => 'qtici_statistics_table', + 'method' => 'replace', + 'effect' => 'fade', + ), + ); + + $tags = _qtici_tag_query(); + $tag_opts = array(); + foreach ($tags as $tag) { + $tag_opts[$tag->tid] = $tag->name; + } + + if (!empty($tag_opts)) { + $form['filters']['tag'] = array( + '#type' => 'select', + '#title' => t('Tag'), + '#options' => array(0 => t('-- Select --')) + $tag_opts, + '#default_value' => '', + '#ajax' => array( + 'callback' => '_qtici_simple_ajax', + 'wrapper' => 'qtici_statistics_table', + 'method' => 'replace', + 'effect' => 'fade', + ), + ); + } + + $form['content'] = array( + '#type' => 'container', + '#prefix' => '<div id="qtici_statistics_table">', + '#suffix' => '</div>', + ); + + if (isset($form_state['values']) && ($form_state['values']['topic'] !== 'NO_TOPIC' || $form_state['values']['level'] !== 'NO_LEVEL' || $form_state['values']['tag'] != 0)) { + $statisticObj = new Statistic(); + // Get the correct variables in case there is no selected + $level = $form_state['values']['level']; + if ($level === 'NO_LEVEL') { + $level = ''; + } + $topic = $form_state['values']['topic']; + if ($topic === 'NO_TOPIC') { + $topic = ''; + } + + $statistics = $statisticObj->getAllStatistics($level, $topic, $form_state['values']['tag']); + } + else { + // By default show all statistics + $statisticObj = new Statistic(); + $statistics = $statisticObj->getAllStatistics(); + } + + $form['content']['statisticsTable'] = array( + '#markup' => _qtici_generalStatistics($statistics), + ); + + return $form; +} + +//make the edit form for the item entity +function qtici_item_entity_edit($form, &$form_state, $entity) { + + $form['item'] = array( + '#type' => 'value', + '#value' => $entity, + ); + + $form['id'] = array( + '#type' => 'hidden', + '#title' => t('id'), + '#required' => TRUE, + '#default_value' => $entity->id, + ); + + $form['sectionid'] = array( + '#type' => 'hidden', + '#required' => TRUE, + '#default_value' => $entity->sectionid, + ); + + $form['title'] = array( + '#type' => 'textfield', + '#title' => t('Title'), + '#required' => TRUE, + '#default_value' => $entity->title, + ); + + $description = unserialize($entity->description); + + $form['description'] = array( + '#type' => 'text_format', + '#title' => t('Description'), + '#required' => FALSE, + '#default_value' => ($description['value']) ? $description['value'] : '', + '#format' => ($description['format']) ? $description['format'] : 'full_html', + ); + + global $_qtici_exercisesTypes; + + $form['type'] = array( + '#type' => 'select', + '#required' => TRUE, + '#title' => t('Type'), + '#description' => t(''), + '#default_value' => $entity->type, + '#options' => $_qtici_exercisesTypes, + ); + + $form['quotation'] = array( + '#type' => 'radios', + '#required' => FALSE, + '#title' => t('Quotation'), + '#description' => t(''), + '#default_value' => $entity->quotation, + '#options' => array( + 'NULL' => t('N.V.T.'), + 'perAnswer' => t('Score per antwoord'), + 'allCorrect' => t('Allemaal juist')), + ); + + $form['question'] = array( + '#type' => 'hidden', + '#required' => FALSE, + '#default_value' => $entity->question, + ); + + $form['max_attempts'] = array( + '#type' => 'textfield', + '#title' => t('Max. Attempts'), + '#required' => FALSE, + '#default_value' => $entity->max_attempts, + ); + + $form['ordering'] = array( + '#type' => 'radios', + '#title' => t('Ordering'), + '#required' => FALSE, + '#description' => t(''), + '#default_value' => $entity->ordering, + '#options' => array( + '1' => t('geordend'), + '0' => t('ongeordend')), + ); + + $form['score'] = array( + '#type' => 'textfield', + '#title' => t('Score'), + '#required' => FALSE, + '#default_value' => $entity->score, + ); + + $counter = 0; + $possibilities = _qtici_loadPossibilitiesByItemID($entity->id); + + foreach ($possibilities as $value) { + + $counter++; + $answer = unserialize($value->answer); + + $form['possibility' . $value->id] = array( + '#type' => 'text_format', + '#title' => t('Mogelijkheid ' . $counter), + '#required' => TRUE, + '#default_value' => $answer['value'], + '#format' => $answer['format'], + ); + } + + field_attach_form('qtici_item', $entity, $form, $form_state); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#submit' => array('qtici_item_form_submit'), + '#weight' => 100, + ); + + return $form; +} + +//make the edit form for the test entity +function qtici_test_entity_edit_form($form, &$form_state, $entity) { + + $form['test'] = array( + '#type' => 'value', + '#value' => $entity, + ); + + $form['id'] = array( + '#type' => 'hidden', + '#required' => TRUE, + '#default_value' => $entity->id, + ); + + $form['olat_testid'] = array( + '#type' => 'hidden', + '#required' => TRUE, + '#default_value' => $entity->olat_testid, + ); + + $form['title'] = array( + '#type' => 'textfield', + '#title' => t('Title'), + '#required' => TRUE, + '#default_value' => $entity->title, + ); + + //check if the value is serialized + $description = unserialize($entity->description); + + $form['description'] = array( + '#type' => 'text_format', + '#title' => t('Description'), + '#required' => FALSE, + '#default_value' => ($description['value']) ? $description['value'] : '', + '#format' => ($description['format']) ? $description['format'] : 'full_html', + ); + + $form['duration'] = array( + '#type' => 'textfield', + '#title' => t('Duration'), + '#required' => FALSE, + '#default_value' => $entity->duration, + ); + + $form['passing_score'] = array( + '#type' => 'textfield', + '#title' => t('Passing Score'), + '#required' => FALSE, + '#default_value' => $entity->passing_score, + ); + + $form['published'] = array( + '#type' => 'radios', + '#title' => t('Published'), + '#required' => FALSE, + '#description' => t(''), + '#default_value' => $entity->published, + '#options' => array( + '1' => t('Published'), + '0' => t('Unpublished')), + ); + + $form['answers_in'] = array( + '#type' => 'radios', + '#title' => t('Toon antwoorden en feedback in de test:'), + '#required' => FALSE, + '#description' => t(''), + '#default_value' => $entity->answers_in, + '#options' => array( + '1' => t('Ja'), + '0' => t('Nee')), + ); + + $form['answers_out'] = array( + '#type' => 'radios', + '#title' => t('Toon antwoorden en feedback na de test:'), + '#required' => FALSE, + '#description' => t(''), + '#default_value' => $entity->answers_out, + '#options' => array( + '1' => t('Ja'), + '0' => t('Nee')), + ); + + $form['show_answer'] = array( + '#type' => 'radios', + '#title' => t('Toon de knop "Laat het juiste antwoord zien.":'), + '#required' => FALSE, + '#description' => t(''), + '#default_value' => $entity->show_answer, + '#options' => array( + '1' => t('Ja'), + '0' => t('Nee')), + ); + + $form['check_answer'] = array( + '#type' => 'radios', + '#title' => t('Toon de knop "Controleer mijn antwoord.":'), + '#required' => FALSE, + '#description' => t(''), + '#default_value' => $entity->check_answer, + '#options' => array( + '1' => t('Ja'), + '0' => t('Nee')), + ); + + $form['course'] = array( + '#type' => 'textfield', + '#title' => t('Course'), + '#required' => FALSE, + '#default_value' => $entity->course, + ); + + global $_qtici_topics; + + $form['topic'] = array( + '#type' => 'select', + '#title' => t('Topic'), + '#options' => $_qtici_topics, + '#required' => TRUE, + '#default_value' => $entity->topic, + ); + + $form['level'] = array( + '#type' => 'radios', + '#title' => t('Level":'), + '#required' => FALSE, + '#default_value' => $entity->level, + '#options' => array( + 'NULL' => t('N.V.T.'), + 'A' => t('A'), + 'B' => t('B'), + 'C' => t('C')), + ); + + field_attach_form('qtici_test', $entity, $form, $form_state); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#submit' => array('qtici_test_form_submit'), + '#weight' => 100, + ); + + return $form; +} + +/** + * Delete Test submit + */ +function _qtici_deleteTest_submit($form, &$form_state) { + $tests = $form_state['values']['overview_table']; + + $feedbackObj = new Feedback(); + + // Prepare array + $ids = array(); + $sectionIDS = array(); + $itemIDS = array(); + $possibilityIDS = array(); + + $testObjs = qtici_test_entity_load_multiple($tests); + foreach ($testObjs as $testObj) { + $ids[] = $testObj->id; + $sectionIDS[] = $testObj->getAllSectionIDsByTest(); + $itemIDS[] = $testObj->getAllItemIDsFromAllSectionsInTest(); + $possibilityIDS = $testObj->allPossibilityIDSFromAllItemsFromAllSectionsInTest($testObj->id); + $testObj->deleteStatistic(); + } + + foreach ($itemIDS[0] as $itemID) { + $feedbackObj->deleteFeedbackByItemID($itemID); + } + + entity_delete_multiple('qtici_possibility', $possibilityIDS); + entity_delete_multiple('qtici_item', $itemIDS[0]); + entity_delete_multiple('qtici_test', $ids); + + _qtici_deleteSections($sectionIDS); + + drupal_set_message(t('Tests have been deleted')); +} + +/** + * Unpublish or publish tests + */ +function _qtici_publishTest_submit($form, &$form_state) { + $tests = $form_state['values']['overview_table']; + $ids = array(); + foreach ($tests as $testid) { + if ($testid != 0) { + $ids[] = (int) $testid; + } + } + + _qtici_publishTest($ids); +} + +/** + * List Tests + */ +function qtici_test_entity_list_entities() { + $content = array(); + // Load all of our entities. + $entities = entity_load_multiple_by_name('qtici_test'); + if (!empty($entities)) { + foreach ($entities as $entity) { + // Create tabular rows for our entities. + $rows[] = array( + 'data' => array( + 'id' => $entity->id, + 'title' => l($entity->title, 'qtici/test/' . $entity->id), + ), + ); + } + // Put our entities into a themed table. See theme_table() for details. + $content['entity_table'] = array( + '#theme' => 'table', + '#rows' => $rows, + '#header' => array(t('ID'), t('Title'),), + ); + } + else { + // There were no entities. Tell the user. + $content[] = array( + '#type' => 'item', + '#markup' => t('No test entities currently exist.'), + ); + } + + return $content; +} diff --git a/functions.inc b/functions.inc new file mode 100755 index 0000000000000000000000000000000000000000..8bfa6630cf142727239225b1675a345226426f38 --- /dev/null +++ b/functions.inc @@ -0,0 +1,620 @@ +<?php + +/** + * @file - Diverse functions + */ + +function strpos_r($haystack, $needle) { + if (strlen($needle) > strlen($haystack)) + trigger_error(sprintf("%s: length of argument 2 must be <= argument 1", __FUNCTION__), E_USER_WARNING); + + $seeks = array(); + while ($seek = strrpos($haystack, $needle)) { + array_push($seeks, $seek); + $haystack = substr($haystack, 0, $seek); + } + return $seeks; +} + +function checkIfExistAndCast($input, $path = '', $folderID = '') { + + if (is_bool($input)) { + if ($input == FALSE) { + $input = 0; + } + } + + if ($input == '' || $input == NULL) { + $input = NULL; + } + else { + if (is_string($input)) { + $input = _formatHTML($input); + $path = str_replace('qti.xml', '', $path); + _qtici_replaceVideo($input, $path); + } + } + + return $input; +} + +function getSecondsFromDuration($input) { + $hours = substr($input, strpos($input, 'T') + 1, ((strpos($input, 'H') - 1 ) - strpos($input, 'T'))); + $minutes = substr($input, strpos($input, 'H') + 1, ((strrpos($input, 'M') - 1 ) - strpos($input, 'H'))); + $seconds = substr($input, strrpos($input, 'M') + 1, ((strpos($input, 'S') - 1 ) - strrpos($input, 'M'))); + + $duration = 0; + $duration += $seconds; + $duration += ($minutes * 60); + $duration += ($hours * 3600); + + return $duration; +} + +function rrmdir($dir) { + foreach (glob($dir . '/{,.}*', GLOB_BRACE) as $file) { + if (substr($file, -2) === '/.' || substr($file, -2) === '..') { + + } + else { + if (is_dir($file)) { + rrmdir($file); + } + else { + unlink($file); + } + } + } + drupal_rmdir($dir); +} + +/** + * Replace old html tags for xhtml new ones + */ +function _formatHTML($string) { + + $replace = array( + '<br>', + //'&', + '<p></p>', + '&nbsp;', + ); + + $with = array( + '<br />', + //'&', + '', + '', + ); + + $newString = str_replace($replace, $with, $string); + + return $newString; +} + +/** + * Returns an array with tests and their ids + * Args: + * - Level + * - Topic + */ +function _qtici_get_exercisesLT($level = '', $topic = '', $tag = 0) { + + $exercises = array(); + + $query = db_select('qtici_test', 't') + ->fields('t', array('id', 'level', 'topic', 'title')); + + if ($level !== '') { + $query->condition('level', $level, '='); + } + + if ($topic !== '') { + $query->condition('topic', $topic, '='); + } + + if ($tag != 0) { + $query->join('taxonomy_entity_index', 'i', 'i.entity_id = t.id'); + $query->condition('i.tid', $tag, '='); + } + + $result = $query->execute(); + + foreach ($result as $test) { + $exercises[] = array( + 'id' => $test->id, + 'title' => $test->title, + 'level' => $test->level + ); + } + + return $exercises; +} + +/** + * Adds a flowplayer element in the $selector (class or id) element + */ +function _qtici_addFlowPlayer($selector, $url, $audio = FALSE) { + + static $mediaplayer_added = FALSE; + static $flowplayer_selectors = array(); + + $config = array(); + $config['flashdir'] = '/' . drupal_get_path('module', 'qtici') . '/js/mediaelement/build/flashmediaelement.swf'; + $config['playlist'] = array( + $url, + ); + + if ($mediaplayer_added === FALSE) { + drupal_add_library('qtici', 'mediaelement'); + drupal_add_library('qtici', 'mediaelement-css'); + } + + if (isset($selector) && !isset($flowplayer_selectors[$selector])) { + drupal_add_js(drupal_get_path('module', 'qtici') . '/js/mediaelement.js', array('type' => 'file', 'scope' => 'footer', 'group' => JS_THEME, 'weight' => 100, 'defer' => TRUE)); + + drupal_add_js(array('flowplayer' => array($selector => $config)), 'setting'); + + $flowplayer_selectors[$selector] = TRUE; + } + + $mediaplayer_added = TRUE; +} + +/** + * Checks if a extension belongs to the defined extensions in global + */ +function _is_in_extensions($ext) { + global $_qtici_extensions; + + return in_array($ext, $_qtici_extensions); +} + +/** + * Returns the fid of the audio, video or image file + */ +function _qtici_getFID($text, $position, $type) { + $length = strlen($type) + 1; + $end = strpos($text, 'fid:', $position); + + $fid = (int) substr($text, $position + $length, $end - $position - $length); + + return $fid; +} + +/** + * Fetch all posibilities for an item + */ +function _qtici_loadPossibilitiesByItemID($id) { + + $query = db_select('qtici_possibility', 'p'); + $query->fields('p', array('id')); + $query->condition('p.itemid', $id); + + $res = $query->execute(); + + $ids = array(); + + foreach ($res as $id) { + $ids[] = $id->id; + } + + return entity_load('qtici_possibility', $ids, array(), FALSE); +} + +/** + * Fetch all item ids of a test + */ +function _qtici_loadItemIDsByTestID($id) { + $query = db_select('qtici_item', 'i'); + $query->fields('i', array('id')); + $query->join('qtici_section', 's', 's.id = i.sectionid'); + $query->join('qtici_test', 't', 't.id = s.testid'); + $query->condition('t.id', $id, '='); + $items = $query->execute()->fetchAssoc(); + + return $items; +} + +/** + * Checks for olat videos in an HTML string, saves them to DB and replaces them with tokens + */ +function _qtici_replaceVideo(&$input, $path) { + // Find videos + $tag = strpos($input, 'class="olatFlashMovieViewer"'); + // Replace them + while ($tag !== FALSE) { + $lenInput = strlen($input); + // Find starting of the closest span (backwards) + $fstSpan = strrpos($input, 'span', $tag - $lenInput) - 1; + // Find closest </span> + $posLastSpan = strpos($input, '</span>', $tag) + 7; + // Get video URL + $dumbVar = substr($input, $fstSpan, $posLastSpan); + $media = null; + if (strpos($dumbVar, 'media/')) { + $dumbVar = substr($dumbVar, strpos($dumbVar, 'media/')); + $dumbVar = substr($dumbVar, 0, strpos($dumbVar, '"')); + $audio = strpos(strtolower($dumbVar), '.mp3'); + global $base_url, $_qtici_extensions, $user; + $uri = $path . $dumbVar; + + // Create file object + $newFile = new stdClass(); + $newFile->uid = $user->uid; + $newFile->status = 1; + $newFile->filename = drupal_basename($uri); + $newFile->uri = $uri; + $newFile->filemime = file_get_mimetype($uri); + $newFile->size = filesize($uri); + $newFile = file_copy($newFile, 'public://', FILE_EXISTS_REPLACE); + $newFile = file_save($newFile); + + if ($audio == TRUE) { + $media = ':audio' . $newFile->fid . 'fid:'; + } + else { + $media = ':video' . $newFile->fid . 'fid:'; + } + } + $input = substr_replace($input, $media, $fstSpan, $posLastSpan - $fstSpan); + $tag = strpos($input, 'class="olatFlashMovieViewer"'); + } + // If there are more "media/" after replacement we asume they are images + if (strpos($input, '"media/')) { + global $base_url; + $path = $base_url . '/' . $path; + $input = str_replace('"media/', '"' . $path . 'media/', $input); + } +} + +/** + * Returns a date of the DB in a nice format + * Args: + * $date (string) + */ +function _qtici_getNiceDate($timestamp) { + $niceDate = date('j M Y H:i', $timestamp); + + return $niceDate; +} + +/** + * Checks if there si video or audio on a string + */ +function _qtici_checkMedia(&$string, $itemid) { + $check = array( + 'video', + 'audio', + ); + $info = array(); + foreach ($check as $value) { + $media = strpos($string, ':' . $value); + while ($media !== FALSE) { + $fid = _qtici_getFID($string, $media, $value); + $file = file_load($fid); + $url = file_create_url($file->uri); + $info[$fid] = array( + 'type' => $value, + 'class' => $value, + ); + // Video tag is not fetched because of filter_xss_admin() if it goes on #title + $replacement = '<div class="video' . $fid . '" data-engine="flash"></div>'; + if ($value == 'audio') { + $replacement = '<div class="audio' . $fid . '"></div>'; + //$replacement = '<div id="player_' . $itemid . '" class="audio' . $fid . '"></div>'; + } + $string = str_replace(':' . $value . $fid . 'fid:', $replacement, $string); + $media = strpos($string, ':' . $value, $media + 1); + } + } + + return $info; +} + +/** + * Checks if there is a textbox and replaces it with a form + */ +function _qtici_checkTextbox(&$string, $DAD = FALSE) { + + $text = strpos($string, ':text'); + while ($text !== FALSE) { + $id = _qtici_getPossID($string, $text, 'text'); + $replacement = '<div class="container-inline"><div class="form-item form-type-textfield form-item-textbox-' . $id . '"><input id="edit-textbox-' . $id . '" class="form-text'; + if ($DAD) { + $replacement .= ' droppable'; + } + $replacement .= '" type="text" style="maxlength: 200px; size: 12px; margin: 3px; width: 250px" value="" name="textbox_' . $id . '" /></div></div>'; + $string = str_replace(':text' . $id . 'box:', $replacement, $string); + $text = strpos($string, ':text', $text + 1); + } +} + +/** + * Returns the fid of the audio, video or image file + */ +function _qtici_getPossID($text, $position, $type) { + $length = strlen($type) + 1; + $end = strpos($text, 'box:', $position); + + $fid = (int) substr($text, $position + $length, $end - $position - $length); + + return $fid; +} + +/** + * Replace ident with id in the content of FiB + */ +function _qtici_setTextbox($string, $ident, $new_id) { + + $replacement = ':text' . $new_id . 'box:'; + $string = str_replace(':text' . $ident . 'box:', $replacement, $string); + + return $string; +} + +/** + * Publishes or unpublishes tests + */ +function _qtici_publishTest($ids) { + + $entities = qtici_test_entity_load_multiple($ids); + + foreach ($entities as $test) { + $status = 0; // Not published + if ($test->published == 0) { + $status = 1; + } + $test->published = $status; + qtici_test_entity_save($test); + } + + drupal_set_message(t('Tests have been (un)published')); +} + +/** + * Makes a label for the check answer callback. This method calculates the score of the student for eacht type of exercise and returns it in a label with text + */ +function qtici_makeLabelFeedback($form_state, $itemid, $returnArray) { + + //get the variables out of the array + $juistFout = $returnArray["trueFalse"]; + $score = $returnArray["score"]; + $numberOfTextboxes = $returnArray["numberOfTextboxes"]; + $label = null; + +//look which quotation type is selected for this item + if ($form_state['values']['quotation_' . $itemid] == "perAnswer") { + + //initialize the variables + $oefScore = $form_state['values']['score_' . $itemid]; + $resultaat = 0; + + //look with type of exercise it is for rating + if ($form_state['values']['type_' . $itemid] == "FIB" || $form_state['values']['type_' . $itemid] == "DAD") { + + //count how much you get for one correct answer + $piece = $oefScore / $numberOfTextboxes; + //multiply the pieces with your score + $resultaat = $piece * $score; + + //look if the score is 0 + if ($score == 0) { + $resultaat = 0; + } + } + else if ($form_state['values']['type_' . $itemid] == "MCQ" || $form_state['values']['type_' . $itemid] == "VID") { + //look if everything is correct + if ($juistFout) { + + //give him the full score + $resultaat = $oefScore; + } + else { + + //if not everything is correct give him his score + $resultaat = $score; + } + } + + //look if the score is negative when yes set it to 0 + if ($resultaat < 0) { + $resultaat = 0; + } + + //Show the label with the correct or false + if ($juistFout) { + $label = "Correct, dit antwoord is goed."; + } + else { + $label = "Fout, uw score is " . number_format($resultaat, 2, ',', ' ') . "!"; + } + } + else { + + //Show the label with the correct or false + if ($juistFout) { + $label = "Correct, dit antwoord is goed."; + } + else { + $label = "Bijna goed, probeer het nog een keer!"; + } + } + + return $label; +} + + +// Theme for single radio. +function theme_qtici_item_form_radio($variables) { + $element = $variables['elements']; + $element['#attributes']['type'] = 'radio'; + element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); + + if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) { + $element['#attributes']['checked'] = 'checked'; + } + _form_set_class($element, array('form-radio')); + + return '<input' . drupal_attributes($element['#attributes']) . ' /> ' . $element["#title"] . '<br />'; +} + +// Theme for radio group. +function theme_qtici_item_form_radios($variables) { + + $element = $variables['elements']; + $attributes = array(); + if (isset($element['#id'])) { + $attributes['id'] = $element['#id']; + } + $attributes['class'] = 'form-radios'; + if (!empty($element['#attributes']['class'])) { + $attributes['class'] .= ' ' . implode(' ', $element['#attributes']['class']); + } + if (isset($element['#attributes']['title'])) { + $attributes['title'] = $element['#attributes']['title']; + } + + $output = ''; + //This duplicates the div: '<div' . drupal_attributes($attributes) . '><br />' . (!empty($element['#children']) ? $element['#children'] : ''); + + $keys = array_keys($element['#options']); + foreach ($keys as $key) { + // Each radios is theme by calling our custom 'my_radio' theme function. + $output .= theme('qtici_item_form_radio', $element[$key]); + } + + //$output .= '</div>'; + + return $output; +} + +// Theme for single checkbox. +function theme_qtici_item_form_checkbox($variables) { + $element = $variables['elements']; + $t = get_t(); + $element['#attributes']['type'] = 'checkbox'; + element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); + + // Unchecked checkbox has #value of integer 0. + if (!empty($element['#checked'])) { + $element['#attributes']['checked'] = 'checked'; + } + _form_set_class($element, array('form-checkbox')); + + return '<input' . drupal_attributes($element['#attributes']) . ' /> ' . $element["#title"] . '<br />'; +} + +// Theme for checkbox group. +function theme_qtici_item_form_checkboxes($variables) { + $element = $variables['elements']; + $attributes = array(); + if (isset($element['#id'])) { + $attributes['id'] = $element['#id']; + } + $attributes['class'][] = 'form-checkboxes'; + if (!empty($element['#attributes']['class'])) { + $attributes['class'] = array_merge($attributes['class'], $element['#attributes']['class']); + } + if (isset($element['#attributes']['title'])) { + $attributes['title'] = $element['#attributes']['title']; + } + + $output = ''; + //This duplicates the div: $output = '<div' . drupal_attributes($attributes) . '><br />' . (!empty($element['#children']) ? $element['#children'] : ''); + + $keys = array_keys($element['#options']); + foreach ($keys as $key) { + // Each radios is theme by calling our custom 'my_radio' theme function. + $output .= theme('qtici_item_form_checkbox', $element[$key]); + } + + //$output .= '</div>'; + + return $output; +} + +/** + * Find answers for marker exercises from the solution string + */ +function _qtici_findAnswers($string) { + + $answers = array(); + + $find = explode('<span style="text-decoration: underline;">', $string); + foreach ($find as $value) { + $length = strpos($value, '</span>'); + $answers[] = substr($value, 0, $length); + } + + return $answers; +} + +/** + * Title callback for statistics form + */ +function qtici_statistics_title($test) { + return t('Statistics for @title', array('@title' => $test->title)); +} + +/** + * Get all tests + */ +function _qtici_getAllTests() { + $query = db_select('qtici_test', 't'); + $query->fields('t'); + $query->orderBy('published', 'DESC'); + $query->orderBy('title'); + $results = $query->execute(); + + return $results; +} + +/** + * Delete section by IDS + */ +function _qtici_deleteSections($ids = array()) { + foreach ($ids as $id) { + $sectionObj = new Section(); + $sectionObj->myConstruct($id); + $sectionObj->deleteSection(); + } +} + +/** + * Gets the correct possibility for an item + */ +function _qtici_getCorrectPossibilityForItem($itemid) { + $query = db_select('qtici_possibility', 'p'); + $query->fields('p', array('answer')); + $query->condition('p.itemid', $itemid, '='); + $query->condition('p.is_correct', 1, '='); + $result = $query->execute(); + + $return = array(); + foreach ($result as $answer) { + $answer_u = unserialize($answer->answer); + $return[] = $answer_u['value']; + } + + return $return; +} + +/** + * Returns the text description without the categories and an array by ref. with them + */ +function _qtici_get_testDescription($string, &$categories) { + $pos = strpos('<categories>', $string); + $description = $string; + // Check if categories are defined inside description + if ($pos) { + $end = strpos('</categories>', $string); + $tag_list = substr($string, $pos + strlen('<categories>'), $end); + $tag_array = explode(',', $tag_list); + + $description = substr($string, 0, $pos); + $categories += $tag_array; + } + + return $description; +} diff --git a/globals.inc b/globals.inc new file mode 100644 index 0000000000000000000000000000000000000000..92c8b8a27d56883f464f0428004a3bc72e42430e --- /dev/null +++ b/globals.inc @@ -0,0 +1,85 @@ +<?php +/** + * @file - Globals + */ + +/** + * Media extensions to be stored in the DB + */ +global $_qtici_extensions; +$_qtici_extensions = array( + 'mp4', + 'mp3', + 'ogg', + 'webm', + 'flv', +); + +/** + * Question types defined in the xml + */ +global $_qtici_questionTypes; +$_qtici_questionTypes = array( + 0 => 'SCQ', // Single Choice + 1 => 'MCQ', // Multiple Choice + 2 => 'FIB', // Fill In Blanks + 3 => 'KPRIM', // Faulttolerance +); + +/** + * Topics + */ +global $_qtici_topics; +$_qtici_topics = array( + 'herhaling' => 'Herhaling', + 'verstavaardigheid' => 'Verstavaardigheid', + 'variatie' => 'Variatie', +); + +/** + * Levels + */ +global $_qtici_levels; +$_qtici_levels = array( + 'A' => t('A1/2'), + 'B' => t('B1/2'), + 'C' => t('C1/2'), +); + +/** + * Exercises types + */ +global $_qtici_exercisesTypes; +$_qtici_exercisesTypes = array( + 'SCQ' => t('SCQ'), + 'MCQ' => t('MCQ'), + 'FIB' => t('FIB'), + 'KPRIM' => t('KPRIM'), + 'DAD' => t('DAD'), + 'MRK' => t('MRK'), + 'VID' => t('VID'), + 'REC' => t('REC'), +); + +/** + * Exercises classes + */ +global $_qtici_exercisesClasses; +$_qtici_exercisesClasses = array( + 'SCQ' => 'SingleChoiceQuestion', + 'MCQ' => 'MultipleChoiceQuestion', + 'FIB' => 'FillInQuestion', + 'KPRIM' => 'KPRIMQuestion', + 'DAD' => 'DragAndDropQuestion', + 'MRK' => 'MarkerQuestion', + 'VID' => 'VideoQuestion', + 'REC' => 'RecorderQuestion', +); + +/** + * Taxonomy configuration (temporal) + */ +global $_qtici_vid; +global $_qtici_field_name; +$_qtici_vid = 1; +$_qtici_field_name = 'field_tags'; diff --git a/handlers/views_handler_field_description.inc b/handlers/views_handler_field_description.inc new file mode 100644 index 0000000000000000000000000000000000000000..7e94a2a607edb3e0a7929f62ec6babfe8c1e8f8a --- /dev/null +++ b/handlers/views_handler_field_description.inc @@ -0,0 +1,22 @@ +<?php +/** + * A handler to display the description with html + * + * @ingroup views_field_handlers + */ +class views_handler_field_description extends views_handler_field_serialized { + function render($values) { + $value = $values->{$this->field_alias}; + + if ($this->options['format'] == 'unserialized') { + return check_plain(print_r(unserialize($value), TRUE)); + } + elseif ($this->options['format'] == 'key' && !empty($this->options['key'])) { + $value = (array) unserialize($value); + //var_dump($value[$this->options['key']]); + return $value[$this->options['key']]; + } + + return $value; + } +} diff --git a/js/DADQuestion.js b/js/DADQuestion.js new file mode 100644 index 0000000000000000000000000000000000000000..460c3ab56f72c652efb07d2b13e4d8c8490e491b --- /dev/null +++ b/js/DADQuestion.js @@ -0,0 +1,76 @@ +(function ($) { + Drupal.behaviors.qticiDAD = { + attach: function (context, settings) { + if ($("div.draggable").length > 0) { + $("div.draggable").draggable({ + revert: function(droppableObj) { + if(droppableObj === false) { + $(this).data("ui-draggable").originalPosition = { + top: 0, + left: 0 + }; + var textbox = $(this).data("ui-droppable"); + $(textbox).width(74); + $(textbox).val(''); + $(this).removeClass("dropped"); + + var selects = $('body').find('div.options'); + selects.each(function(index, el) { + var children = el.children; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if ($(child).hasClass("dropped")) { + var textbox = $(child).data("ui-droppable"); + $(child).position({ + of: $(textbox), + my: 'left top', + at: 'left top' + }); + } + } + }); + + return true; + } else { + var selects = $('body').find('div.options'); + selects.each(function(index, el) { + var children = el.children; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if ($(child).hasClass("dropped")) { + var textbox = $(child).data("ui-droppable"); + $(child).position({ + of: $(textbox), + my: 'left top', + at: 'left top' + }); + } + } + }); + + return false; + } + } + }); + } + //$("input.droppable").css("color","white"); + if ($("div.draggable").length > 0) { + $("input.droppable").droppable({ + drop: function(event, ui) { + ui.draggable.position({ + of: $(this), + my: 'left top', + at: 'left top' + }); + $(this).draggable(); + $(this).draggable("widget").val(ui.draggable.text()); + ui.draggable.data("ui-droppable", this); + ui.draggable.addClass("dropped"); + $(this).width(ui.draggable.width()); + $(this).height(ui.draggable.height()); + } + }); + } + } + } +})(jQuery); diff --git a/js/filter.js b/js/filter.js new file mode 100755 index 0000000000000000000000000000000000000000..4615c7836217c050fa440393be4808ae94d8303d --- /dev/null +++ b/js/filter.js @@ -0,0 +1,33 @@ +(function ($) { + var asInitVals = new Array(); + $(window).load(function(){ + var oTable = $(".example255").dataTable(); + + $("tfoot input").keyup( function () { + /* Filter on the column (the index) of this element */ + oTable.fnFilter( this.value, $("tfoot input").index(this) ); + } ); + + /* + * Support functions to provide a little bit of "user friendlyness" to the textboxes in + * the footer + */ + $("tfoot input").each( function (i) { + asInitVals[i] = this.value; + } ); + + $("tfoot input").focus( function () { + if ( this.className == "search_init" ) { + this.className = ""; + this.value = ""; + } + } ); + + $("tfoot input").blur( function (i) { + if ( this.value == "" ) { + this.className = "search_init"; + this.value = asInitVals[$("tfoot input").index(this)]; + } + } ); + }); +})(jQuery); diff --git a/js/left_menu.js b/js/left_menu.js new file mode 100755 index 0000000000000000000000000000000000000000..dea7f7a49599058a0c2d7932d061c049a16437c0 --- /dev/null +++ b/js/left_menu.js @@ -0,0 +1,64 @@ +(function($) { + Drupal.behaviors.qtici = { + attach: function(context, settings) { + // If it is a test show only the corresponding menu + if (typeof settings.qtici !== 'undefined' && typeof settings.qtici.test !== 'undefined') { + var level = Drupal.settings.qtici.level; + var levels = ["A", "B", "C"]; + for (i = 0; i < levels.length; i++) { + if (levels[i] !== level) { + $("#block-qtici-qtici_exercises_" + levels[i]).hide(); + } + } + var topic = Drupal.settings.qtici.topic; + $(".topic_" + topic).children().each(function () { + var name = $("a", this).attr("class"); + if (name === 'qtici-' + topic) { + $("ul", this).show(); + } + }); + + if (typeof settings.qtici !== 'undefined' && typeof settings.qtici.testId !== 'undefined') { + var testId = Drupal.settings.qtici.testId; + $("ul.block_exercises li ul li").children().each(function () { + var urlStr = $(this).attr("href"); + var name = urlStr.split('/').splice(-1,1); + if (name == testId) { + $(this).css("color", "#ce1433"); + } + }); + } + } + else { + + if (typeof settings.qtici !== 'undefined' && typeof settings.qtici.topic !== 'undefined') { + var topic = Drupal.settings.qtici.topic; + $("ul.block_exercises").children().each(function () { + var name = $("a", this).attr("class"); + if (name === 'qtici-' + topic) { + $("ul", this).show(); + } + }); + } + if (typeof settings.qtici !== 'undefined' && typeof settings.qtici.level !== 'undefined') { + var level = Drupal.settings.qtici.level; + $("ul.block_exercises li ul li").children().each(function () { + var name = $(this).attr("class"); + if (name !== 'qtici-' + level) { + $(this).hide(); + } + }); + } + } + + $("ul.block_exercises li").click( function() { + var displayed = jQuery("ul", this).css("display"); + if (displayed === "none") { + $("ul", this).slideDown(150); + } else { + $("ul", this).slideUp(150); + }; + }); + } + } +})(jQuery); diff --git a/js/markerQuestion.js b/js/markerQuestion.js new file mode 100755 index 0000000000000000000000000000000000000000..1653256d1d718128af5c0e257678423aa938ad4b --- /dev/null +++ b/js/markerQuestion.js @@ -0,0 +1,50 @@ +(function ($) { + Drupal.behaviors.qticiMRK = { + attach: function (context, settings) { + $('[id^=selectable_]').each(function() { + var currentId = $(this).attr('id'); + var toRemove = "selectable_"; + var id = currentId.replace(toRemove,''); + + var text = $("#"+currentId).text() + text = text.trim() + text = text.split("") + text = text.join("</span><span class=\"ui-widget-content_"+id+"\">"); + $("#"+currentId).html("<span class=\"ui-widget-content_"+id+"\">" + text + "</span>"); + $('#selectable_'+id).addClass("selectable_style"); + + /*$('#selectable_'+id+' span').each(function() { + $(this).html($(this).text().replace(' ', ' ')); + });*/ + + $( "#selectable_"+id ).bind("mousedown", function(e) { + e.metaKey = true; + }).selectable({ + filter: "span.ui-widget-content_" +id, + stop: function() { + var c = 0; + var woord ="" ; + var result = $( "#selectable_"+id ).next('span').empty(); + $( ".ui-selected", this ).each(function() { + var index = $( "#selectable_"+ id +" span" ).index( this ); + if(c == 0) { + result.append( "" + ( index + 1 ) ); + } else { + result.append( "-" + ( index + 1 ) ); + } + + letter = index + 1; + if(c == 0){ + woord += ''+letter; + c++; + } else { + woord += '-'+letter; + } + }); + $('input[name=MRK_hidden_'+ id +']').val(woord); + } + }); + }); + } + } +})(jQuery); diff --git a/js/mediaelement.js b/js/mediaelement.js new file mode 100644 index 0000000000000000000000000000000000000000..a5891efa67e38a7841c7bef50871262ceb6357b0 --- /dev/null +++ b/js/mediaelement.js @@ -0,0 +1,65 @@ +(function ($) { + Drupal.behaviors.mediaplayer = { + attach: function(context, settings) { + // onload + jQuery.each(settings.flowplayer, function(selector, config) { + + var type = 0; + if (selector.substring(0, 5) == 'video') { + type = 1; + } + var audioDiv = document.getElementsByClassName(selector); + $(audioDiv).each(function(index) { + if ($(this).className != 'mediaplayer-processed') { + var audioTag = '<' + selector.substring(0, 5); + if (type == 1) { + audioTag = audioTag + ' width="480" height="270" controls="controls" preload="auto"'; + } + audioTag = audioTag + ' id="' + selector + index + '">'; + var playlist; + var sourceType; + for (var i = 0; i < config['playlist'].length; i++) { + playlist = config['playlist'][i].replace("%20", " "); + sourceType = playlist.split('.').pop().toLowerCase(); + if (sourceType == 'mp3') { + sourceType = 'mpeg'; + } + audioTag = audioTag + '<source type="' + selector.substring(0, 5) + '/' + sourceType + '" src="' + playlist + '" />'; + if (type == 1) { + flashdir = config['flashdir']; + audioTag = audioTag + '<object type="application/x-shockwave-flash" data="' + flashdir + '"><<param name="movie" value="flashmediaelement.swf" /><param name="flashvars" value="controls=true&file=' + selector.substring(0, 5) + '" /></object>'; + } + }; + audioTag = audioTag + '</' + selector.substring(0, 5) + '>'; + $(this).removeClass(selector).addClass(selector + index); + $(this).html(audioTag); + config_me = { + // enables Flash and Silverlight to resize to content size + enableAutosize: true, + // the order of controls you want on the control bar (and other plugins below) + features: ['playpause','progress','duration','volume'], + // Hide controls when playing and mouse is not over the video + alwaysShowControls: true, + }; + + if (type == 0) { + // Add audio config + config_me.audioWidth = 400; + config_me.audioHeight = 30; + config_me.loop = false; + } + else { + // Add video config + config_me.defaultVideoWidth = 480; + config_me.defaultVideoHeight = 270; + config_me.videoWidth = -1; + config_me.videoHeight = -1; + } + + $('#' + selector + index).addClass('mediaplayer-processed').mediaelementplayer(config_me); + } + }); + }); + } + } +})(jQuery); diff --git a/js/mediaelement/.gitattributes b/js/mediaelement/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..7e474b4169373a0861e5a70c7e8b43a1bd948916 --- /dev/null +++ b/js/mediaelement/.gitattributes @@ -0,0 +1,8 @@ +* text=auto eol=lf + +*.jar binary +*.mp3 binary +*.mp4 binary +*.ogv binary +*.png binary +*.webm binary \ No newline at end of file diff --git a/js/mediaelement/.gitignore b/js/mediaelement/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..8397110f121121e4c8e05b1102fd20bcbec37556 --- /dev/null +++ b/js/mediaelement/.gitignore @@ -0,0 +1,10 @@ +# silverlight stuff +src/silverlight/obj/* +src/silverlight/Bin/* +build/AppManifest.xaml +build/SilverlightMediaElementTestPage.html +build/SilverlightMediaElement.pdb +build/SilverlightMediaElement.dll +tests/* +newfeatures/* +/mejs3/ diff --git a/js/mediaelement/Gruntfile.js b/js/mediaelement/Gruntfile.js new file mode 100644 index 0000000000000000000000000000000000000000..bc8c1e120d18c34603d9b223dfdd1d7c24f00a45 --- /dev/null +++ b/js/mediaelement/Gruntfile.js @@ -0,0 +1,179 @@ +module.exports = function(grunt) { + + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-shell'); + grunt.loadNpmTasks('grunt-text-replace'); + grunt.loadNpmTasks("grunt-remove-logging"); + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + concat: { + me: { + src: [ + 'src/js/me-header.js', + 'src/js/me-namespace.js', + 'src/js/me-utility.js', + 'src/js/me-plugindetector.js', + 'src/js/me-featuredetection.js', + 'src/js/me-mediaelements.js', + 'src/js/me-shim.js', + 'src/js/me-i18n.js', + 'src/js/me-i18n-locale-de.js', + 'src/js/me-i18n-locale-zh.js' + ], + dest: 'local-build/mediaelement.js' + }, + mep: { + src: [ + 'src/js/mep-header.js', + 'src/js/mep-library.js', + 'src/js/mep-player.js', + 'src/js/mep-feature-playpause.js', + 'src/js/mep-feature-stop.js', + 'src/js/mep-feature-progress.js', + 'src/js/mep-feature-time.js', + 'src/js/mep-feature-volume.js', + 'src/js/mep-feature-fullscreen.js', + 'src/js/mep-feature-speed.js', + 'src/js/mep-feature-tracks.js', + 'src/js/mep-feature-contextmenu.js', + 'src/js/mep-feature-postroll.js' + ], + dest: 'local-build/mediaelementplayer.js' + }, + bundle: { + src: [ + 'local-build/mediaelement.js', + 'local-build/mediaelementplayer.js' + ], + dest: 'local-build/mediaelement-and-player.js' + } + }, + removelogging: { + dist: { + src: [ + 'local-build/mediaelement.js', + 'local-build/mediaelementplayer.js', + 'local-build/mediaelement-and-player.js' + ] + }, + options: { + // Keep `warn` and other methods from the console API + methods: ['log'] + } + }, + uglify: { + me: { + src : ['local-build/mediaelement.js'], + dest : 'local-build/mediaelement.min.js', + banner : 'src/js/me-header.js' + }, + mep: { + src : ['local-build/mediaelementplayer.js'], + dest : 'local-build/mediaelementplayer.min.js', + banner : 'src/js/mep-header.js' + }, + bundle: { + src : ['local-build/mediaelement-and-player.js'], + dest : 'local-build/mediaelement-and-player.min.js' + }, + options: { + // Preserve comments that start with a bang (like the file header) + preserveComments: "some" + } + }, + cssmin: { + build: { + src : ['src/css/mediaelementplayer.css'], + dest : 'local-build/mediaelementplayer.min.css' + } + }, + copy: { + build: { + expand : true, + cwd : 'src/css/', + src : ['*.png', '*.svg', '*.gif', '*.css'], + dest : 'local-build/', + flatten : true, + filter : 'isFile' + } + }, + replace: { + cdnBuild: { + src: ['src/flash/FlashMediaElement.as'], + dest: 'tmp/FlashMediaElement.as', + replacements: [{ + from: '//Security.allowDomain("*");', + to: 'Security.allowDomain("*");' + }, { + from: '//Security.allowInsecureDomain("*");', + to: 'Security.allowInsecureDomain("*");' + }] + } + }, + clean: { + build: ['local-build'], + temp: ['tmp'] + }, + + // Task that compiles flashmediaelement.swf using the free Flex SDK on Linux/Mac. + // There are a few prerequisite steps involved in running this task. + // + // 1) Install the Flex SDK version 4.6 (only needs to be done once) + // Download the free flex sdk from http://sourceforge.net/adobe/flexsdk/wiki/Download%20Flex%204.6/ + // Unzip it to a directory on your local machine (eg: /usr/local/flex_sdk_4.6) + // Create a symlink from the install location to this directory + // (eg: ln -s /usr/local/flex_sdk_4.6 mediaelement/src/flash) + // + // 2) Update the `flexPath` variable below to reflect the name of the symlink you created + // + // 3) Build the SWC file (only needs to be done if you have changed something in the FLA file) + // Open the FlashMediaElement.fla file in the Flash Professional IDE. + // Goto the menu item 'File->Publish Settings' + // Click the 'Flash' tab + // Make sure the box 'Export SWC' is checked + // Click 'Publish' + // Quit out of the Flash IDE + // + // 4) Run this task from the command line: `grunt shell:buildFlash` + // + // Note: There doesn't seem to be an available SWC library that defines the YouTube player API. + // Remove the -strict compiler option to see warnings coming from YouTube API calls. + // + flexPath: '../flex_sdk_4.6', + buildFlashCommand: [ + '<%= flexPath %>/bin/mxmlc -strict=false -warnings=true', + '<%= flashIn %> -o <%= flashOut %>', + '-library-path+="<%= flexPath %>/lib"', + '-include-libraries+=src/flash/flashmediaelement.swc', + '-include-libraries+=src/flash/flashls.swc -use-network=true', + '-headless-server -static-link-runtime-shared-libraries' + ].join(" "), + + shell: { + buildFlash: { + command: function() { + grunt.config.set("flashIn", 'src/flash/FlashMediaElement.as'); + grunt.config.set("flashOut", 'local-build/flashmediaelement.swf'); + return grunt.config.get("buildFlashCommand"); + } + }, + buildFlashCDN: { + command: function() { + grunt.config.set("flashIn", 'tmp/FlashMediaElement.as'); + grunt.config.set("flashOut", 'local-build/flashmediaelement-cdn.swf'); + return grunt.config.get("buildFlashCommand"); + } + } + } + }); + + + grunt.registerTask('default', ['concat', 'removelogging', 'uglify', 'cssmin', 'copy', + 'shell:buildFlash', 'replace:cdnBuild', 'shell:buildFlashCDN', 'clean:temp']); + +}; \ No newline at end of file diff --git a/js/mediaelement/README.md b/js/mediaelement/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b385c79c52b6e689d0f68a1bcf7f41f94080278a --- /dev/null +++ b/js/mediaelement/README.md @@ -0,0 +1,110 @@ +# `<video>` and `<audio>` made easy. + +One file. Any browser. Same UI. + +* Author: John Dyer [http://j.hn/](http://j.hn/) +* Website: [http://mediaelementjs.com/](http://mediaelementjs.com/) +* License: [MIT](http://johndyer.mit-license.org/) +* Meaning: Use everywhere, keep copyright, it'd be swell if you'd link back here. +* Thanks: my employer, [Dallas Theological Seminary](http://www.dts.edu/) +* Contributors: [mikesten](https://github.com/mikesten), [sylvinus](https://github.com/sylvinus), [mattfarina](https://github.com/mattfarina), [romaninsh](https://github.com/romaninsh), [fmalk](https://github.com/fmalk), [jeffrafter](https://github.com/jeffrafter), [sompylasar](https://github.com/sompylasar), [andyfowler](https://github.com/andyfowler), [RobRoy](https://github.com/RobRoy), [jakearchibald](https://github.com/jakearchibald), [seanhellwig](https://github.com/seanhellwig), [CJ-Jackson](https://github.com/CJ-Jackson), [kaichen](https://github.com/kaichen), [gselva](https://github.com/gselva), [erktime](https://github.com/erktime), [bradleyboy](https://github.com/bradleyboy), [kristerkari](https://github.com/kristerkari), [rmhall](https://github.com/rmhall), [tantalic](https://github.com/tantalic), [madesign](http://github.com/madesign), [aschempp](http://github.com/aschempp), [gavinlynch](https://github.com/gavinlynch), [Birol2010](http://github.com/Birol2010), tons of others (see [pulls](https://github.com/SuriyaaKudoIsc/MediaElement.js/pulls)) + + +## Installation and Usage + +_MediaElementPlayer: HTML5 `<video>` and `<audio>` player_ + +A complete HTML/CSS audio/video player built on top `MediaElement.js` and `jQuery`. Many great HTML5 players have a completely separate Flash UI in fallback mode, but MediaElementPlayer.js uses the same HTML/CSS for all players. + +## Change Log + +Changes available at [changelog.md] + +### 1. Add Script and Stylesheet +```html +<script src="jquery.js"></script> +<script src="mediaelement-and-player.min.js"></script> +<link rel="stylesheet" href="mediaelementplayer.css" /> +``` +### 2. Add `<video>` or `<audio>` tags +If your users have JavaScript and/or Flash, the easist route for all browsers and mobile devices is to use a single MP4 or MP3 file. + +```html +<video src="myvideo.mp4" width="320" height="240"></video> +``` +```html +<video src="myaudio.mp3"></video> +``` + +#### Optional: multiple codecs +This includes multiple codecs for various browsers (H.264 for IE9+, Safari, and Chrome, WebM for Firefox 4 and Opera, Ogg for Firefox 3). + +```html +<video width="320" height="240" poster="poster.jpg" controls="controls" preload="none"> + <source type="video/mp4" src="myvideo.mp4" /> + <source type="video/webm" src="myvideo.webm" /> + <source type="video/ogg" src="myvideo.ogv" /> +</video> +``` + +#### Optional: Browsers with JavaScript disabled +In very rare cases, you might have an non-HTML5 browser with Flash turned on and JavaScript turned off. In that specific case, you can also include the Flash `<object>` code. +```html +<video width="320" height="240" poster="poster.jpg" controls="controls" preload="none"> + <source type="video/mp4" src="myvideo.mp4" /> + <source type="video/webm" src="myvideo.webm" /> + <source type="video/ogg" src="myvideo.ogv" /> + <object width="320" height="240" type="application/x-shockwave-flash" data="flashmediaelement.swf"> + <param name="movie" value="flashmediaelement.swf" /> + <param name="flashvars" value="controls=true&poster=myvideo.jpg&file=myvideo.mp4" /> + <img src="myvideo.jpg" width="320" height="240" title="No video playback capabilities" /> + </object> +</video> +``` + +### 3. Startup + +#### Automatic start +You can avoid running any startup scripts by added `class="mejs-player"` to the `<video>` or `<audio>` tag. Options can be added using the `data-mejsoptions` attribute +```html +<video src="myvideo.mp4" width="320" height="240" + class="mejs-player" + data-mejsoptions='{"alwaysShowControls": true}'></video> +``` + +#### Normal JavaScirpt +```html +<script> +var player = new MediaElementPlayer('#player', {success: function(mediaElement, originalNode) { + // do things +}}); +</script> +``` + +#### jQuery plugin +```html +<script> +$('video').mediaelementplayer({success: function(mediaElement, originalNode) { + // do things +}}); +</script> +``` + +## How it Works: +_MediaElement.js: HTML5 `<video>` and `<audio>` shim_ + +`MediaElement.js` is a set of custom Flash and Silverlight plugins that mimic the HTML5 MediaElement API for browsers that don't support HTML5 or don't support the media codecs you're using. +Instead of using Flash as a _fallback_, Flash is used to make the browser seem HTML5 compliant and enable codecs like H.264 (via Flash) and even WMV (via Silverlight) on all browsers. +```html +<script src="mediaelement.js"></script> +<video src="myvideo.mp4" width="320" height="240"></video> + +<script> +var v = document.getElementsByTagName("video")[0]; +new MediaElement(v, {success: function(media) { + media.play(); +}}); +</script> +``` +You can use this as a standalone library if you wish, or just stick with the full MediaElementPlayer. + diff --git a/js/mediaelement/bower.json b/js/mediaelement/bower.json new file mode 100644 index 0000000000000000000000000000000000000000..5a4c6645d67ed2e7772f29aad6816781364ee644 --- /dev/null +++ b/js/mediaelement/bower.json @@ -0,0 +1,26 @@ +{ + "name": "mediaelement", + "version": "2.15.1", + "homepage": "https://github.com/herby/mediaelement", + "description": "HTML5 <video> and <audio> made easy.", + "moduleType": [ + "globals" + ], + "keywords": [ + "html5", + "video", + "audio", + "shim" + ], + "license": "MIT", + "ignore": [ + "**", + "!/build/**", + "!/bower.json", + "!/README*" + ], + "main": [ + "./build/mediaelement-and-player.js", + "./build/mediaelementplayer.css" + ] +} diff --git a/js/mediaelement/build/DO NOT CHANGE THESE FILES. USE -src- FOLDER.txt b/js/mediaelement/build/DO NOT CHANGE THESE FILES. USE -src- FOLDER.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/js/mediaelement/build/background.png b/js/mediaelement/build/background.png new file mode 100644 index 0000000000000000000000000000000000000000..fd428412ae26af13dab448ec833b1cb603e37ee9 Binary files /dev/null and b/js/mediaelement/build/background.png differ diff --git a/js/mediaelement/build/bigplay.fw.png b/js/mediaelement/build/bigplay.fw.png new file mode 100644 index 0000000000000000000000000000000000000000..66d0e3cb73ceec0b1ffc7cdd4c1bc5edaf960556 Binary files /dev/null and b/js/mediaelement/build/bigplay.fw.png differ diff --git a/js/mediaelement/build/bigplay.png b/js/mediaelement/build/bigplay.png new file mode 100644 index 0000000000000000000000000000000000000000..694553e31c387188b6bde397a5200c212aff2dc5 Binary files /dev/null and b/js/mediaelement/build/bigplay.png differ diff --git a/js/mediaelement/build/bigplay.svg b/js/mediaelement/build/bigplay.svg new file mode 100644 index 0000000000000000000000000000000000000000..2b7817005d90d5b2357616d7dd210c14819d2178 --- /dev/null +++ b/js/mediaelement/build/bigplay.svg @@ -0,0 +1,14 @@ +<?xml version="1.0" standalone="no"?> +<svg id="bigplay" viewBox="0 0 100 200" style="background-color:#ffffff00" version="1.1" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" + x="0px" y="0px" width="100px" height="200px" +> + <g id="dark"> + <path id="Polygon" d="M 72.5 49.5 L 38.75 68.9856 L 38.75 30.0144 L 72.5 49.5 Z" fill="#ffffff" opacity="0.75" /> + <path id="Ellipse" d="M 13 50.5 C 13 29.7891 29.7891 13 50.5 13 C 71.2109 13 88 29.7891 88 50.5 C 88 71.2109 71.2109 88 50.5 88 C 29.7891 88 13 71.2109 13 50.5 Z" stroke="#ffffff" stroke-width="5" fill="none" opacity="0.75"/> + </g> + <g id="light"> + <path id="Polygon2" d="M 72.5 149.5 L 38.75 168.9856 L 38.75 130.0144 L 72.5 149.5 Z" fill="#ffffff" opacity="1.0" /> + <path id="Ellipse2" d="M 13 150.5 C 13 129.7891 29.7891 113 50.5 113 C 71.2109 113 88 129.7891 88 150.5 C 88 171.211 71.2109 188 50.5 188 C 29.7891 188 13 171.211 13 150.5 Z" stroke="#ffffff" stroke-width="5" fill="none" opacity="1.0"/> + </g> +</svg> \ No newline at end of file diff --git a/js/mediaelement/build/controls-ted.png b/js/mediaelement/build/controls-ted.png new file mode 100644 index 0000000000000000000000000000000000000000..3aac05aa83cb7fed54831a19d85a8c267e939720 Binary files /dev/null and b/js/mediaelement/build/controls-ted.png differ diff --git a/js/mediaelement/build/controls-wmp-bg.png b/js/mediaelement/build/controls-wmp-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..89bb9b95602ecfca6290f6006f817da365da7b90 Binary files /dev/null and b/js/mediaelement/build/controls-wmp-bg.png differ diff --git a/js/mediaelement/build/controls-wmp.png b/js/mediaelement/build/controls-wmp.png new file mode 100644 index 0000000000000000000000000000000000000000..4775ef5b02faf2b826d35dfc72511b6b27fea87b Binary files /dev/null and b/js/mediaelement/build/controls-wmp.png differ diff --git a/js/mediaelement/build/controls.fw.png b/js/mediaelement/build/controls.fw.png new file mode 100644 index 0000000000000000000000000000000000000000..e27682ae107408748ac91805eee0e1554d618cd5 Binary files /dev/null and b/js/mediaelement/build/controls.fw.png differ diff --git a/js/mediaelement/build/controls.png b/js/mediaelement/build/controls.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a857d800b64264443af4609e0ebf7175593d8f Binary files /dev/null and b/js/mediaelement/build/controls.png differ diff --git a/js/mediaelement/build/controls.svg b/js/mediaelement/build/controls.svg new file mode 100644 index 0000000000000000000000000000000000000000..af3bd41606ae3f0f8d20d213f9e8819569bb7715 --- /dev/null +++ b/js/mediaelement/build/controls.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?> <!-- Generator: Adobe Fireworks CS6, Export SVG Extension by Aaron Beall (http://fireworks.abeall.com) . Version: 0.6.1 --> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg id="controls.fw-Page%201" viewBox="0 0 144 32" style="background-color:#ffffff00" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" x="0px" y="0px" width="144px" height="32px" > <defs> <radialGradient id="gradient1" cx="50%" cy="50%" r="50%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#f2f2f2" stop-opacity="0.2" offset="100%"/> </radialGradient> <linearGradient id="gradient2" x1="50%" y1="-7.8652%" x2="50%" y2="249.6629%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient3" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient4" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient5" x1="50%" y1="-33.3333%" x2="50%" y2="152.0833%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient6" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient7" x1="50%" y1="-33.3333%" x2="50%" y2="152.0833%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient8" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient9" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient10" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient11" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient12" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient13" x1="40%" y1="-140%" x2="40%" y2="98.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient14" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient15" x1="60%" y1="-140%" x2="60%" y2="98.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient16" x1="50%" y1="0%" x2="50%" y2="298.4375%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient17" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient18" x1="50%" y1="-200%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient19" x1="50%" y1="-200%" x2="50%" y2="110.9375%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient20" x1="55%" y1="0%" x2="55%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient21" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="99.4444%"/> </linearGradient> </defs> <g id="BG"> </g> <g id="controls"> <path id="Line" d="M 98.5 7.5 L 109.5 7.5 " stroke="#ffffff" stroke-width="1" fill="none"/> <path id="Line2" d="M 98.5 3.5 L 109.5 3.5 " stroke="#ffffff" stroke-width="1" fill="none"/> <path id="Line3" d="M 98.5 11.5 L 109.5 11.5 " stroke="#ffffff" stroke-width="1" fill="none"/> <path id="Ellipse" d="M 108 11.5 C 108 10.6716 108.4477 10 109 10 C 109.5523 10 110 10.6716 110 11.5 C 110 12.3284 109.5523 13 109 13 C 108.4477 13 108 12.3284 108 11.5 Z" fill="#ffffff"/> <path id="Ellipse2" d="M 104 7.5 C 104 6.6716 104.4477 6 105 6 C 105.5523 6 106 6.6716 106 7.5 C 106 8.3284 105.5523 9 105 9 C 104.4477 9 104 8.3284 104 7.5 Z" fill="#ffffff"/> <path id="Ellipse3" d="M 108 3.5 C 108 2.6716 108.4477 2 109 2 C 109.5523 2 110 2.6716 110 3.5 C 110 4.3284 109.5523 5 109 5 C 108.4477 5 108 4.3284 108 3.5 Z" fill="#ffffff"/> </g> <g id="backlight"> <g id="off"> <rect x="83" y="21" width="10" height="6" stroke="#ffffff" stroke-width="1" fill="#333333"/> </g> <g id="on"> <path id="Ellipse4" d="M 81 8 C 81 5.2385 84.134 3 88 3 C 91.866 3 95 5.2385 95 8 C 95 10.7615 91.866 13 88 13 C 84.134 13 81 10.7615 81 8 Z" fill="url(#gradient1)"/> <rect x="83" y="5" width="10" height="6" stroke="#ffffff" stroke-width="1" fill="#333333"/> </g> </g> <g id="loop"> <g id="on2"> <path d="M 73.795 4.205 C 75.2155 4.8785 76.2 6.3234 76.2 8 C 76.2 10.3196 74.3196 12.2 72 12.2 C 69.6804 12.2 67.8 10.3196 67.8 8 C 67.8 6.3234 68.7845 4.8785 70.205 4.205 L 68.875 2.875 C 67.1501 3.9289 66 5.8306 66 8 C 66 11.3138 68.6862 14 72 14 C 75.3138 14 78 11.3138 78 8 C 78 5.8306 76.8499 3.9289 75.125 2.875 L 73.795 4.205 Z" fill="url(#gradient2)"/> <path d="M 71 2 L 66 2 L 71 7 L 71 2 Z" fill="url(#gradient3)"/> </g> <g id="off2"> <path d="M 73.795 20.205 C 75.2155 20.8785 76.2 22.3234 76.2 24 C 76.2 26.3196 74.3196 28.2 72 28.2 C 69.6804 28.2 67.8 26.3196 67.8 24 C 67.8 22.3234 68.7845 20.8785 70.205 20.205 L 68.875 18.875 C 67.1501 19.9289 66 21.8306 66 24 C 66 27.3138 68.6862 30 72 30 C 75.3138 30 78 27.3138 78 24 C 78 21.8306 76.8499 19.9289 75.125 18.875 L 73.795 20.205 Z" fill="#a8a8b7"/> <path d="M 71 18 L 66 18 L 71 23 L 71 18 Z" fill="#a8a8b7"/> </g> </g> <g id="cc"> <rect visibility="hidden" x="49" y="2" width="14" height="12" stroke="#b0b0b0" stroke-width="1" fill="none"/> <text visibility="hidden" x="49" y="17" width="14" fill="#ffffff" style="font-size: 10px; color: #ffffff; font-family: Arial; text-align: center; "><tspan><![CDATA[cc]]></tspan></text> <path d="M 55 7 C 50.2813 3.7813 50.063 12.9405 55 10 " stroke="#ffffff" stroke-width="1" fill="none"/> <path d="M 60 7 C 55.2813 3.7813 55.063 12.9405 60 10 " stroke="#ffffff" stroke-width="1" fill="none"/> <path d="M 50 3 L 62 3 L 62 13 L 50 13 L 50 3 ZM 49 2 L 49 14 L 63 14 L 63 2 L 49 2 Z" fill="url(#gradient4)"/> <rect x="49" y="2" width="14" height="12" fill="none"/> </g> <g id="volume"> <g id="no%20sound"> <rect x="17" y="5" width="5" height="6" fill="url(#gradient5)"/> <path d="M 21 5 L 25 2 L 25 14 L 21 11.0625 L 21 5 Z" fill="url(#gradient6)"/> </g> <g id="sound%20bars"> <rect x="17" y="21" width="5" height="6" fill="url(#gradient7)"/> <path d="M 21 21 L 25 18 L 25 30 L 21 27.0625 L 21 21 Z" fill="url(#gradient8)"/> <path d="M 27 18 C 27 18 30.0625 17.375 30 24 C 29.9375 30.625 27 30 27 30 " stroke="#ffffff" stroke-width="1" fill="none"/> <path d="M 26 21.0079 C 26 21.0079 28.041 20.6962 27.9994 24 C 27.9577 27.3038 26 26.9921 26 26.9921 " stroke="#ffffff" stroke-width="1" fill="none"/> </g> </g> <g id="play/pause"> <g id="play"> <path id="Polygon" d="M 14 8.5 L 3 14 L 3 3 L 14 8.5 Z" fill="url(#gradient9)"/> </g> <g id="pause"> <rect x="3" y="18" width="3" height="12" fill="url(#gradient10)"/> <rect x="10" y="18" width="3" height="12" fill="url(#gradient11)"/> </g> </g> <g id="fullscreen"> <g id="enter%201"> <path d="M 34 2 L 39 2 L 34 7 L 34 2 Z" fill="url(#gradient12)"/> <path d="M 34 14 L 39 14 L 34 9 L 34 14 Z" fill="url(#gradient13)"/> <path d="M 46 2 L 41 2 L 46 7 L 46 2 Z" fill="url(#gradient14)"/> <path d="M 46 14 L 41 14 L 46 9 L 46 14 Z" fill="url(#gradient15)"/> </g> <g id="exit"> <path d="M 42 22 L 46 22 L 42 18 L 42 22 Z" fill="url(#gradient16)"/> <path d="M 38 22 L 38 18 L 34 22 L 38 22 Z" fill="url(#gradient17)"/> <path d="M 38 26 L 34 26 L 38 30 L 38 26 Z" fill="url(#gradient18)"/> <path d="M 42 26 L 42 30 L 46 26 L 42 26 Z" fill="url(#gradient19)"/> </g> </g> <g id="stop"> <rect x="115" y="3" width="10" height="10" fill="url(#gradient20)"/> </g> <g id="chooser"> <path d="M 135.2346 6.1522 C 136.2551 5.7295 137.4251 6.2141 137.8478 7.2346 C 138.2704 8.2551 137.7859 9.425 136.7654 9.8478 C 135.7449 10.2705 134.5749 9.7859 134.1522 8.7654 C 133.7295 7.7449 134.2141 6.5749 135.2346 6.1522 ZM 133.2735 1.4176 L 136 4.0054 L 138.7265 1.4176 L 138.8246 5.1754 L 142.5824 5.2735 L 139.9946 8 L 142.5824 10.7265 L 138.8246 10.8246 L 138.7265 14.5824 L 136 11.9946 L 133.2735 14.5824 L 133.1754 10.8246 L 129.4176 10.7265 L 132.0054 8 L 129.4176 5.2735 L 133.1754 5.1754 L 133.2735 1.4176 Z" fill="url(#gradient21)"/> </g> </svg> \ No newline at end of file diff --git a/js/mediaelement/build/flashmediaelement-cdn.swf b/js/mediaelement/build/flashmediaelement-cdn.swf new file mode 100755 index 0000000000000000000000000000000000000000..9b222e9cf6799975243bf9621ebe76bdcecbf6b3 Binary files /dev/null and b/js/mediaelement/build/flashmediaelement-cdn.swf differ diff --git a/js/mediaelement/build/flashmediaelement.swf b/js/mediaelement/build/flashmediaelement.swf new file mode 100755 index 0000000000000000000000000000000000000000..5998d67699f32fb155a763f7e92373f4c1879aff Binary files /dev/null and b/js/mediaelement/build/flashmediaelement.swf differ diff --git a/js/mediaelement/build/jquery.js b/js/mediaelement/build/jquery.js new file mode 100644 index 0000000000000000000000000000000000000000..86a330515eede7c169280718decc9de4ddb494fb --- /dev/null +++ b/js/mediaelement/build/jquery.js @@ -0,0 +1,9597 @@ +/*! + * jQuery JavaScript Library v1.9.1 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-2-4 + */ +(function( window, undefined ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +//"use strict"; +var + // The deferred used on DOM ready + readyList, + + // A central reference to the root jQuery(document) + rootjQuery, + + // Support: IE<9 + // For `typeof node.method` instead of `node.method !== undefined` + core_strundefined = typeof undefined, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + location = window.location, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // [[Class]] -> type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.9.1", + + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // The ready event handler + completed = function( event ) { + + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } + }, + // Clean-up method for dom ready events + detach = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call(obj) ] || "object" : + typeof obj; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); + }, + + parseJSON: function( data ) { + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + if ( data === null ) { + return data; + } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + core_push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function() { + + var support, all, a, + input, select, fragment, + opt, eventName, isSupported, i, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>"; + + // Support tests won't run in some limited or non-browser environments + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + if ( !all || !a || !all.length ) { + return {}; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + support = { + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.5/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + checkOn: !!input.value, + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Tests for enctype support on a form (#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>", + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: document.compatMode === "CSS1Compat", + + // Will be defined later + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + body.appendChild( container ).appendChild( div ); + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>"; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement("div") ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== core_strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + div.innerHTML = "<div></div>"; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + if ( support.inlineBlockNeedsLayout ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + return support; +})(); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var i, l, thisCache, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var attrs, name, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; + + if ( !name.indexOf( "data-" ) ) { + name = jQuery.camelCase( name.slice(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + // Try to fetch any internally stored data first + return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + } + + this.each(function() { + jQuery.data( this, key, value ); + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + hooks.cur = fn; + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + }); + } + + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + }); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.match( core_rnotwhite ) || []; + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var ret, hooks, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val, + self = jQuery(this); + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var hooks, notxml, ret, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === core_strundefined ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + // In IE9+, Flash objects don't have .getAttribute (#12945) + // Support: IE9+ + if ( typeof elem.getAttribute !== core_strundefined ) { + ret = elem.getAttribute( name ); + } + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( rboolean.test( name ) ) { + // Set corresponding property to false for boolean attributes + // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8 + if ( !getSetAttribute && ruseDefault.test( name ) ) { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } else { + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + var + // Use .prop to determine if this attribute is understood as boolean + prop = jQuery.prop( elem, name ), + + // Fetch it accordingly + attr = typeof prop === "boolean" && elem.getAttribute( name ), + detail = typeof prop === "boolean" ? + + getSetInput && getSetAttribute ? + attr != null : + // oldIE fabricates an empty string for missing boolean attributes + // and conflates checked/selected into attroperties + ruseDefault.test( name ) ? + elem[ jQuery.camelCase( "default-" + name ) ] : + !!attr : + + // fetch an attribute node for properties not recognized as boolean + elem.getAttributeNode( name ); + + return detail && detail.value !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; + +// fix oldIE value attroperty +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return jQuery.nodeName( elem, "input" ) ? + + // Ignore the value *property* by using defaultValue + elem.defaultValue : + + ret && ret.specified ? ret.value : undefined; + }, + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ? + ret.value : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret == null ? undefined : ret; + } + }); + }); + + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + event.isTrigger = true; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG <use> instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur != this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + } + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== document.activeElement && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === document.activeElement && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === core_strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); +/*! + * Sizzle CSS Selector Engine + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ +(function( window, undefined ) { + +var i, + cachedruns, + Expr, + getText, + isXML, + compile, + hasDuplicate, + outermostContext, + + // Local document vars + setDocument, + document, + docElem, + documentIsXML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + sortOrder, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + support = {}, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Array methods + arr = [], + pop = arr.pop, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = "([*^$|!~]?=)", + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rsibling = /[\x20\t\r\n\f]*[+~]/, + + rnative = /^[^{]+\{\s*\[native code/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, + funescape = function( _, escaped ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + return high !== high ? + escaped : + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Use a stripped-down slice if we can't use a native one +try { + slice.call( preferredDoc.documentElement.childNodes, 0 )[0].nodeType; +} catch ( e ) { + slice = function( i ) { + var elem, + results = []; + while ( (elem = this[i++]) ) { + results.push( elem ); + } + return results; + }; +} + +/** + * For feature detection + * @param {Function} fn The function to test for native support + */ +function isNative( fn ) { + return rnative.test( fn + "" ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var cache, + keys = []; + + return (cache = function( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key ] = value); + }); +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return fn( div ); + } catch (e) { + return false; + } finally { + // release memory in IE + div = null; + } +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( !documentIsXML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) { + push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); + return results; + } + } + + // QSA path + if ( support.qsa && !rbuggyQSA.test(selector) ) { + old = true; + nid = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, slice.call( newContext.querySelectorAll( + newSelector + ), 0 ) ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsXML = isXML( doc ); + + // Check if getElementsByTagName("*") returns only elements + support.tagNameNoComments = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if attributes should be retrieved by attribute nodes + support.attributes = assert(function( div ) { + div.innerHTML = "<select></select>"; + var type = typeof div.lastChild.getAttribute("multiple"); + // IE8 returns a string for some attributes even when not present + return type !== "boolean" && type !== "string"; + }); + + // Check if getElementsByClassName can be trusted + support.getByClassName = assert(function( div ) { + // Opera can't find a second classname (in 9.6) + div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>"; + if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { + return false; + } + + // Safari 3.2 caches class attributes and doesn't catch changes + div.lastChild.className = "e"; + return div.getElementsByClassName("e").length === 2; + }); + + // Check if getElementById returns elements by name + // Check if getElementsByName privileges form controls or returns elements by ID + support.getByName = assert(function( div ) { + // Inject content + div.id = expando + 0; + div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>"; + docElem.insertBefore( div, docElem.firstChild ); + + // Test + var pass = doc.getElementsByName && + // buggy browsers will return fewer than the correct 2 + doc.getElementsByName( expando ).length === 2 + + // buggy browsers will return more than the correct 0 + doc.getElementsByName( expando + 0 ).length; + support.getIdNotName = !doc.getElementById( expando ); + + // Cleanup + docElem.removeChild( div ); + + return pass; + }); + + // IE6/7 return modified attributes + Expr.attrHandle = assert(function( div ) { + div.innerHTML = "<a href='#'></a>"; + return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute("href") === "#"; + }) ? + {} : + { + "href": function( elem ) { + return elem.getAttribute( "href", 2 ); + }, + "type": function( elem ) { + return elem.getAttribute("type"); + } + }; + + // ID find and filter + if ( support.getIdNotName ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + + return m ? + m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? + [m] : + undefined : + []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.tagNameNoComments ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Name + Expr.find["NAME"] = support.getByName && function( tag, context ) { + if ( typeof context.getElementsByName !== strundefined ) { + return context.getElementsByName( name ); + } + }; + + // Class + Expr.find["CLASS"] = support.getByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) { + return context.getElementsByClassName( className ); + } + }; + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21), + // no need to also add to buggyMatches since matches checks buggyQSA + // A support test would require too much code (would include document ready) + rbuggyQSA = [ ":focus" ]; + + if ( (support.qsa = isNative(doc.querySelectorAll)) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explictly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = "<select><option selected=''></option></select>"; + + // IE8 - Some boolean attributes are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Opera 10-12/IE8 - ^= $= *= and empty values + // Should not select anything + div.innerHTML = "<input type='hidden' i=''/>"; + if ( div.querySelectorAll("[i^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = new RegExp( rbuggyMatches.join("|") ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + var compare; + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) { + if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { + if ( a === doc || contains( preferredDoc, a ) ) { + return -1; + } + if ( b === doc || contains( preferredDoc, b ) ) { + return 1; + } + return 0; + } + return compare & 4 ? -1 : 1; + } + + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + // Always assume the presence of duplicates if sort doesn't + // pass them to our comparison function (as in Google Chrome). + hasDuplicate = false; + [0, 0].sort( sortOrder ); + support.detectDuplicates = hasDuplicate; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + // rbuggyQSA always contains :focus, so no need for an existence check + if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + var val; + + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( !documentIsXML ) { + name = name.toLowerCase(); + } + if ( (val = Expr.attrHandle[ name ]) ) { + return val( elem ); + } + if ( documentIsXML || support.attributes ) { + return elem.getAttribute( name ); + } + return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? + name : + val && val.specified ? val.value : null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +// Document sorting and removing duplicates +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + i = 1, + j = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( ; (elem = results[i]); i++ ) { + if ( elem === results[ i - 1 ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +// Returns a function to use in pseudos for input types +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +// Returns a function to use in pseudos for buttons +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +// Returns a function to use in pseudos for positionals +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[4] ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeName ) { + if ( nodeName === "*" ) { + return function() { return true; }; + } + + nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifider + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsXML ? + elem.getAttribute("xml:lang") || elem.getAttribute("lang") : + elem.lang) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push( { + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && !documentIsXML && + Expr.relative[ tokens[1].type ] ) { + + context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0]; + if ( !context ) { + return results; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, slice.call( seed, 0 ) ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + documentIsXML, + results, + rsibling.test( selector ) + ); + return results; +} + +// Deprecated +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Easy API for creating new setFilters +function setFilters() {} +Expr.filters = setFilters.prototype = Expr.pseudos; +Expr.setFilters = new setFilters(); + +// Initialize with the default document +setDocument(); + +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, ret, self, + len = this.length; + + if ( typeof selector !== "string" ) { + self = this; + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + ret = []; + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, this[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = ( this.selector ? this.selector + " " : "" ) + selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true) ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + cur = this[i]; + + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + } + cur = cur.parentNode; + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +jQuery.fn.andSelf = jQuery.fn.addBack; + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( this.length > 1 && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /<tbody/i, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + manipulation_rcheckableType = /^(?:checkbox|radio)$/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "<select multiple='multiple'>", "</select>" ], + legend: [ 1, "<fieldset>", "</fieldset>" ], + area: [ 1, "<map>", "</map>" ], + param: [ 1, "<object>", "</object>" ], + thead: [ 1, "<table>", "</table>" ], + tr: [ 2, "<table><tbody>", "</tbody></table>" ], + col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], + td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1></$2>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function( value ) { + var isFunc = jQuery.isFunction( value ); + + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( !isFunc && typeof value !== "string" ) { + value = jQuery( value ).not( this ).detach(); + } + + return this.domManip( [ value ], true, function( elem ) { + var next = this.nextSibling, + parent = this.parentNode; + + if ( parent ) { + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + }); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, table ? self.html() : undefined ); + } + self.domManip( args, table, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( + table && jQuery.nodeName( this[i], "table" ) ? + findOrAppend( this[i], "tbody" ) : + this[i], + node, + i + ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery.ajax({ + url: node.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + var attr = elem.getAttributeNode("type"); + elem.type = ( attr && attr.specified ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted <tbody> from table fragments + if ( !jQuery.support.tbody ) { + + // String was a <table>, *may* have spurious <tbody> + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare <thead> or <tfoot> + wrap[1] === "<table>" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + } +}); +var iframe, getStyles, curCSS, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + var bool = typeof state === "boolean"; + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var num, val, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("<iframe frameborder='0' width='0' height='0'/>") + .css( "cssText", "display:block !important" ) + ).appendTo( doc.documentElement ); + + // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse + doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document; + doc.write("<!doctype html><html><body>"); + doc.close(); + + display = actualDisplay( nodeName, doc ); + iframe.detach(); + } + + // Store the correct default display + elemdisplay[ nodeName ] = display; + } + + return display; +} + +// Called ONLY from within css_defaultDisplay +function actualDisplay( name, doc ) { + var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + display = jQuery.css( elem[0], "display" ); + elem.remove(); + return display; +} + +jQuery.each([ "height", "width" ], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + // certain elements can have dimension info if we invisibly show them + // however, it must have a current display style that would benefit from this + return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ? + jQuery.swap( elem, cssShow, function() { + return getWidthOrHeight( elem, name, extra ); + }) : + getWidthOrHeight( elem, name, extra ); + } + }, + + set: function( elem, value, extra ) { + var styles = extra && getStyles( elem ); + return setPositiveNumber( elem, value, extra ? + augmentWidthOrHeight( + elem, + name, + extra, + jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + styles + ) : 0 + ); + } + }; +}); + +if ( !jQuery.support.opacity ) { + jQuery.cssHooks.opacity = { + get: function( elem, computed ) { + // IE uses filters for opacity + return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? + ( 0.01 * parseFloat( RegExp.$1 ) ) + "" : + computed ? "1" : ""; + }, + + set: function( elem, value ) { + var style = elem.style, + currentStyle = elem.currentStyle, + opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", + filter = currentStyle && currentStyle.filter || style.filter || ""; + + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + style.zoom = 1; + + // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652 + // if value === "", then remove inline opacity #12685 + if ( ( value >= 1 || value === "" ) && + jQuery.trim( filter.replace( ralpha, "" ) ) === "" && + style.removeAttribute ) { + + // Setting style.filter to null, "" & " " still leave "filter:" in the cssText + // if "filter:" is present at all, clearType is disabled, we want to avoid this + // style.removeAttribute is IE Only, but so apparently is this code path... + style.removeAttribute( "filter" ); + + // if there is no filter style applied in a css rule or unset inline opacity, we are done + if ( value === "" || currentStyle && !currentStyle.filter ) { + return; + } + } + + // otherwise, set new filter values + style.filter = ralpha.test( filter ) ? + filter.replace( ralpha, opacity ) : + filter + " " + opacity; + } + }; +} + +// These hooks cannot be added until DOM ready because the support test +// for it is not run until after DOM ready +jQuery(function() { + if ( !jQuery.support.reliableMarginRight ) { + jQuery.cssHooks.marginRight = { + get: function( elem, computed ) { + if ( computed ) { + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + // Work around by temporarily setting element display to inline-block + return jQuery.swap( elem, { "display": "inline-block" }, + curCSS, [ elem, "marginRight" ] ); + } + } + }; + } + + // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 + // getComputedStyle returns percent when specified for top/left/bottom/right + // rather than make the css module depend on the offset module, we just check for it here + if ( !jQuery.support.pixelPosition && jQuery.fn.position ) { + jQuery.each( [ "top", "left" ], function( i, prop ) { + jQuery.cssHooks[ prop ] = { + get: function( elem, computed ) { + if ( computed ) { + computed = curCSS( elem, prop ); + // if curCSS returns percentage, fallback to offset + return rnumnonpx.test( computed ) ? + jQuery( elem ).position()[ prop ] + "px" : + computed; + } + } + }; + }); + } + +}); + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + // Support: Opera <= 12.12 + // Opera reports offsetWidths and offsetHeights less than zero on some elements + return elem.offsetWidth <= 0 && elem.offsetHeight <= 0 || + (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none"); + }; + + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); + }; +} + +// These hooks are used by animate to expand properties +jQuery.each({ + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // assumes a single number if not a string + parts = typeof value === "string" ? value.split(" ") : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( !rmargin.test( prefix ) ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +}); +var r20 = /%20/g, + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +jQuery.fn.extend({ + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map(function(){ + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + }) + .filter(function(){ + var type = this.type; + // Use .is(":disabled") so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !manipulation_rcheckableType.test( type ) ); + }) + .map(function( i, elem ){ + var val = jQuery( this ).val(); + + return val == null ? + null : + jQuery.isArray( val ) ? + jQuery.map( val, function( val ){ + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }) : + { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }).get(); + } +}); + +//Serialize an array of form elements or a set of +//key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value ); + s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ).replace( r20, "+" ); +}; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( jQuery.isArray( obj ) ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // Item is non-scalar (array or object), encode its numeric index. + buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && jQuery.type( obj ) === "object" ) { + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; +}); + +jQuery.fn.hover = function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); +}; +var + // Document location + ajaxLocParts, + ajaxLocation, + ajax_nonce = jQuery.now(), + + ajax_rquery = /\?/, + rhash = /#.*$/, + rts = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/, + + // Keep a copy of the old load method + _load = jQuery.fn.load, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat("*"); + +// #8138, IE may throw an exception when accessing +// a field from window.location if document.domain has been set +try { + ajaxLocation = location.href; +} catch( e ) { + // Use the href attribute of an A element + // since IE will modify it given document.location + ajaxLocation = document.createElement( "a" ); + ajaxLocation.href = ""; + ajaxLocation = ajaxLocation.href; +} + +// Segment location into parts +ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || []; + + if ( jQuery.isFunction( func ) ) { + // For each dataType in the dataTypeExpression + while ( (dataType = dataTypes[i++]) ) { + // Prepend if requested + if ( dataType[0] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + (structure[ dataType ] = structure[ dataType ] || []).unshift( func ); + + // Otherwise append + } else { + (structure[ dataType ] = structure[ dataType ] || []).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + }); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var deep, key, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +jQuery.fn.load = function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + } + + var selector, response, type, + self = this, + off = url.indexOf(" "); + + if ( off >= 0 ) { + selector = url.slice( off, url.length ); + url = url.slice( 0, off ); + } + + // If it's a function + if ( jQuery.isFunction( params ) ) { + + // We assume that it's the callback + callback = params; + params = undefined; + + // Otherwise, build a param string + } else if ( params && typeof params === "object" ) { + type = "POST"; + } + + // If we have elements to modify, make the request + if ( self.length > 0 ) { + jQuery.ajax({ + url: url, + + // if "type" variable is undefined, then "GET" method will be used + type: type, + dataType: "html", + data: params + }).done(function( responseText ) { + + // Save response for use in complete callback + response = arguments; + + self.html( selector ? + + // If a selector was specified, locate the right elements in a dummy div + // Exclude scripts to avoid IE 'Permission Denied' errors + jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) : + + // Otherwise use the full result + responseText ); + + }).complete( callback && function( jqXHR, status ) { + self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] ); + }); + } + + return this; +}; + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){ + jQuery.fn[ type ] = function( fn ){ + return this.on( type, fn ); + }; +}); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + // shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return jQuery.ajax({ + url: url, + type: method, + dataType: type, + data: data, + success: callback + }); + }; +}); + +jQuery.extend({ + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: ajaxLocation, + type: "GET", + isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /xml/, + html: /html/, + json: /json/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var // Cross-domain detection vars + parts, + // Loop variable + i, + // URL without anti-cache param + cacheURL, + // Response headers as string + responseHeadersString, + // timeout handle + timeoutTimer, + + // To know if global events are to be dispatched + fireGlobals, + + transport, + // Response headers + responseHeaders, + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + // Callbacks context + callbackContext = s.context || s, + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks("once memory"), + // Status-dependent callbacks + statusCode = s.statusCode || {}, + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + // The jqXHR state + state = 0, + // Default abort message + strAbort = "canceled", + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( state === 2 ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( (match = rheaders.exec( responseHeadersString )) ) { + responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match == null ? null : match; + }, + + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + var lname = name.toLowerCase(); + if ( !state ) { + name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( !state ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( state < 2 ) { + for ( code in map ) { + // Lazy-add the new callback in a way that preserves old ones + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } else { + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ).complete = completeDeferred.add; + jqXHR.success = jqXHR.done; + jqXHR.error = jqXHR.fail; + + // Remove hash character (#7531: and string promotion) + // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""]; + + // A cross-domain request is in order when we have a protocol:host:port mismatch + if ( s.crossDomain == null ) { + parts = rurl.exec( s.url.toLowerCase() ); + s.crossDomain = !!( parts && + ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] || + ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != + ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) + ); + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( state === 2 ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + fireGlobals = s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger("ajaxStart"); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + cacheURL = s.url; + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // If data is available, append data to url + if ( s.data ) { + cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data ); + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add anti-cache in url if needed + if ( s.cache === false ) { + s.url = rts.test( cacheURL ) ? + + // If there is already a '_' parameter, set its value + cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) : + + // Otherwise add one to the end + cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++; + } + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? + s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { + // Abort if not done already and return + return jqXHR.abort(); + } + + // aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + for ( i in { success: 1, error: 1, complete: 1 } ) { + jqXHR[ i ]( s[ i ] ); + } + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout(function() { + jqXHR.abort("timeout"); + }, s.timeout ); + } + + try { + state = 1; + transport.send( requestHeaders, done ); + } catch ( e ) { + // Propagate exception as error if not done + if ( state < 2 ) { + done( -1, e ); + // Simply rethrow otherwise + } else { + throw e; + } + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Called once + if ( state === 2 ) { + return; + } + + // State is "done" now + state = 2; + + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // If successful, handle type chaining + if ( status >= 200 && status < 300 || status === 304 ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader("Last-Modified"); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader("etag"); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 ) { + isSuccess = true; + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + isSuccess = true; + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + isSuccess = ajaxConvert( s, response ); + statusText = isSuccess.state; + success = isSuccess.data; + error = isSuccess.error; + isSuccess = !error; + } + } else { + // We extract error from statusText + // then normalize statusText and status for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger("ajaxStop"); + } + } + } + + return jqXHR; + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + } +}); + +/* Handles responses to an ajax request: + * - sets all responseXXX fields accordingly + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + var firstDataType, ct, finalDataType, type, + contents = s.contents, + dataTypes = s.dataTypes, + responseFields = s.responseFields; + + // Fill responseXXX fields + for ( type in responseFields ) { + if ( type in responses ) { + jqXHR[ responseFields[type] ] = responses[ type ]; + } + } + + // Remove auto dataType and get content-type in the process + while( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader("Content-Type"); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +// Chain conversions given the request and the original response +function ajaxConvert( s, response ) { + var conv2, current, conv, tmp, + converters = {}, + i = 0, + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(), + prev = dataTypes[ 0 ]; + + // Apply the dataFilter if provided + if ( s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + // Convert to each sequential dataType, tolerating list modification + for ( ; (current = dataTypes[++i]); ) { + + // There's only work to do if current dataType is non-auto + if ( current !== "*" ) { + + // Convert response if prev dataType is non-auto and differs from current + if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split(" "); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.splice( i--, 0, current ); + } + + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s["throws"] ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; + } + } + } + } + + // Update prev for next iteration + prev = current; + } + } + + return { state: "success", data: response }; +} +// Install script dataType +jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /(?:java|ecma)script/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +}); + +// Handle cache's special case and global +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + s.global = false; + } +}); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + + var script, + head = document.head || jQuery("head")[0] || document.documentElement; + + return { + + send: function( _, callback ) { + + script = document.createElement("script"); + + script.async = true; + + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function( _, isAbort ) { + + if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if ( script.parentNode ) { + script.parentNode.removeChild( script ); + } + + // Dereference the script + script = null; + + // Callback if not abort + if ( !isAbort ) { + callback( 200, "success" ); + } + } + }; + + // Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending + // Use native DOM manipulation to avoid our domManip AJAX trickery + head.insertBefore( script, head.firstChild ); + }, + + abort: function() { + if ( script ) { + script.onload( undefined, true ); + } + } + }; + } +}); +var oldCallbacks = [], + rjsonp = /(=)\?(?=&|$)|\?\?/; + +// Default jsonp settings +jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function() { + var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) ); + this[ callback ] = true; + return callback; + } +}); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { + + var callbackName, overwritten, responseContainer, + jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ? + "url" : + typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data" + ); + + // Handle iff the expected data type is "jsonp" or we have a parameter to set + if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { + + // Get callback name, remembering preexisting value associated with it + callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? + s.jsonpCallback() : + s.jsonpCallback; + + // Insert callback into url or form data + if ( jsonProp ) { + s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); + } else if ( s.jsonp !== false ) { + s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; + } + + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( !responseContainer ) { + jQuery.error( callbackName + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // force json dataType + s.dataTypes[ 0 ] = "json"; + + // Install callback + overwritten = window[ callbackName ]; + window[ callbackName ] = function() { + responseContainer = arguments; + }; + + // Clean-up function (fires after converters) + jqXHR.always(function() { + // Restore preexisting value + window[ callbackName ] = overwritten; + + // Save back as free + if ( s[ callbackName ] ) { + // make sure that re-using the options doesn't screw things around + s.jsonpCallback = originalSettings.jsonpCallback; + + // save the callback name for future use + oldCallbacks.push( callbackName ); + } + + // Call if it was a function and we have a response + if ( responseContainer && jQuery.isFunction( overwritten ) ) { + overwritten( responseContainer[ 0 ] ); + } + + responseContainer = overwritten = undefined; + }); + + // Delegate to script + return "script"; + } +}); +var xhrCallbacks, xhrSupported, + xhrId = 0, + // #5280: Internet Explorer will keep connections alive if we don't abort on unload + xhrOnUnloadAbort = window.ActiveXObject && function() { + // Abort all pending requests + var key; + for ( key in xhrCallbacks ) { + xhrCallbacks[ key ]( undefined, true ); + } + }; + +// Functions to create xhrs +function createStandardXHR() { + try { + return new window.XMLHttpRequest(); + } catch( e ) {} +} + +function createActiveXHR() { + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch( e ) {} +} + +// Create the request object +// (This is still attached to ajaxSettings for backward compatibility) +jQuery.ajaxSettings.xhr = window.ActiveXObject ? + /* Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ + function() { + return !this.isLocal && createStandardXHR() || createActiveXHR(); + } : + // For all other browsers, use the standard XMLHttpRequest object + createStandardXHR; + +// Determine support properties +xhrSupported = jQuery.ajaxSettings.xhr(); +jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +xhrSupported = jQuery.support.ajax = !!xhrSupported; + +// Create transport if the browser can provide an xhr +if ( xhrSupported ) { + + jQuery.ajaxTransport(function( s ) { + // Cross domain only allowed if supported through XMLHttpRequest + if ( !s.crossDomain || jQuery.support.cors ) { + + var callback; + + return { + send: function( headers, complete ) { + + // Get a new xhr + var handle, i, + xhr = s.xhr(); + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open( s.type, s.url, s.async, s.username, s.password ); + } else { + xhr.open( s.type, s.url, s.async ); + } + + // Apply custom fields if provided + if ( s.xhrFields ) { + for ( i in s.xhrFields ) { + xhr[ i ] = s.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( s.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( s.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !s.crossDomain && !headers["X-Requested-With"] ) { + headers["X-Requested-With"] = "XMLHttpRequest"; + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + } catch( err ) {} + + // Do send the request + // This may raise an exception which is actually + // handled in jQuery.ajax (so no try/catch here) + xhr.send( ( s.hasContent && s.data ) || null ); + + // Listener + callback = function( _, isAbort ) { + var status, responseHeaders, statusText, responses; + + // Firefox throws exceptions when accessing properties + // of an xhr when a network error occurred + // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) + try { + + // Was never called and is aborted or complete + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = undefined; + + // Do not keep as active anymore + if ( handle ) { + xhr.onreadystatechange = jQuery.noop; + if ( xhrOnUnloadAbort ) { + delete xhrCallbacks[ handle ]; + } + } + + // If it's an abort + if ( isAbort ) { + // Abort it manually if needed + if ( xhr.readyState !== 4 ) { + xhr.abort(); + } + } else { + responses = {}; + status = xhr.status; + responseHeaders = xhr.getAllResponseHeaders(); + + // When requesting binary data, IE6-9 will throw an exception + // on any attempt to access responseText (#11426) + if ( typeof xhr.responseText === "string" ) { + responses.text = xhr.responseText; + } + + // Firefox throws an exception when accessing + // statusText for faulty cross-domain requests + try { + statusText = xhr.statusText; + } catch( e ) { + // We normalize with Webkit giving an empty statusText + statusText = ""; + } + + // Filter status for non standard behaviors + + // If the request is local and we have data: assume a success + // (success with no data won't get notified, that's the best we + // can do given current implementations) + if ( !status && s.isLocal && !s.crossDomain ) { + status = responses.text ? 200 : 404; + // IE - #1450: sometimes returns 1223 when it should be 204 + } else if ( status === 1223 ) { + status = 204; + } + } + } + } catch( firefoxAccessException ) { + if ( !isAbort ) { + complete( -1, firefoxAccessException ); + } + } + + // Call complete if needed + if ( responses ) { + complete( status, statusText, responses, responseHeaders ); + } + }; + + if ( !s.async ) { + // if we're in sync mode we fire the callback + callback(); + } else if ( xhr.readyState === 4 ) { + // (IE6 & IE7) if it's in cache and has been + // retrieved directly we need to fire the callback + setTimeout( callback ); + } else { + handle = ++xhrId; + if ( xhrOnUnloadAbort ) { + // Create the active xhrs callbacks list if needed + // and attach the unload handler + if ( !xhrCallbacks ) { + xhrCallbacks = {}; + jQuery( window ).unload( xhrOnUnloadAbort ); + } + // Add to list of active xhrs callbacks + xhrCallbacks[ handle ] = callback; + } + xhr.onreadystatechange = callback; + } + }, + + abort: function() { + if ( callback ) { + callback( undefined, true ); + } + } + }; + } + }); +} +var fxNow, timerId, + rfxtypes = /^(?:toggle|show|hide)$/, + rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ), + rrun = /queueHooks$/, + animationPrefilters = [ defaultPrefilter ], + tweeners = { + "*": [function( prop, value ) { + var end, unit, + tween = this.createTween( prop, value ), + parts = rfxnum.exec( value ), + target = tween.cur(), + start = +target || 0, + scale = 1, + maxIterations = 20; + + if ( parts ) { + end = +parts[2]; + unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + + // We need to compute starting value + if ( unit !== "px" && start ) { + // Iteratively approximate from a nonzero starting point + // Prefer the current property, because this process will be trivial if it uses the same units + // Fallback to end or a simple constant + start = jQuery.css( tween.elem, prop, true ) || end || 1; + + do { + // If previous iteration zeroed out, double until we get *something* + // Use a string for doubling factor so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + start = start / scale; + jQuery.style( tween.elem, prop, start + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // And breaking the loop if scale is unchanged or perfect, or if we've just had enough + } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations ); + } + + tween.unit = unit; + tween.start = start; + // If a +=/-= token was provided, we're doing a relative animation + tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end; + } + return tween; + }] + }; + +// Animations created synchronously will run synchronously +function createFxNow() { + setTimeout(function() { + fxNow = undefined; + }); + return ( fxNow = jQuery.now() ); +} + +function createTweens( animation, props ) { + jQuery.each( props, function( prop, value ) { + var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( collection[ index ].call( animation, prop, value ) ) { + + // we're done with this property + return; + } + } + }); +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = animationPrefilters.length, + deferred = jQuery.Deferred().always( function() { + // don't match elem in the :animated selector + delete tick.elem; + }), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ]); + + if ( percent < 1 && length ) { + return remaining; + } else { + deferred.resolveWith( elem, [ animation ] ); + return false; + } + }, + animation = deferred.promise({ + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { specialEasing: {} }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + // if we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // resolve when we played the last frame + // otherwise, reject + if ( gotoEnd ) { + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + }), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length ; index++ ) { + result = animationPrefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + return result; + } + } + + createTweens( animation, props ); + + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + }) + ); + + // attach callbacks from options + return animation.progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); +} + +function propFilter( props, specialEasing ) { + var value, name, index, easing, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( jQuery.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // not quite $.extend, this wont overwrite keys already present. + // also - reusing 'index' from above because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.split(" "); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length ; index++ ) { + prop = props[ index ]; + tweeners[ prop ] = tweeners[ prop ] || []; + tweeners[ prop ].unshift( callback ); + } + }, + + prefilter: function( callback, prepend ) { + if ( prepend ) { + animationPrefilters.unshift( callback ); + } else { + animationPrefilters.push( callback ); + } + } +}); + +function defaultPrefilter( elem, props, opts ) { + /*jshint validthis:true */ + var prop, index, length, + value, dataShow, toggle, + tween, hooks, oldfire, + anim = this, + style = elem.style, + orig = {}, + handled = [], + hidden = elem.nodeType && isHidden( elem ); + + // handle queue: false promises + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always(function() { + // doing this makes sure that the complete handler will be called + // before this completes + anim.always(function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + }); + }); + } + + // height/width overflow pass + if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height animated + if ( jQuery.css( elem, "display" ) === "inline" && + jQuery.css( elem, "float" ) === "none" ) { + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) { + style.display = "inline-block"; + + } else { + style.zoom = 1; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + if ( !jQuery.support.shrinkWrapBlocks ) { + anim.always(function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + }); + } + } + + + // show/hide pass + for ( index in props ) { + value = props[ index ]; + if ( rfxtypes.exec( value ) ) { + delete props[ index ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + continue; + } + handled.push( index ); + } + } + + length = handled.length; + if ( length ) { + dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} ); + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + + // store state if its toggle - enables .stop().toggle() to "reverse" + if ( toggle ) { + dataShow.hidden = !hidden; + } + if ( hidden ) { + jQuery( elem ).show(); + } else { + anim.done(function() { + jQuery( elem ).hide(); + }); + } + anim.done(function() { + var prop; + jQuery._removeData( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + }); + for ( index = 0 ; index < length ; index++ ) { + prop = handled[ index ]; + tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 ); + orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop ); + + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = tween.start; + if ( hidden ) { + tween.end = tween.start; + tween.start = prop === "width" || prop === "height" ? 1 : 0; + } + } + } + } +} + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || "swing"; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + if ( tween.elem[ tween.prop ] != null && + (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) { + return tween.elem[ tween.prop ]; + } + + // passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails + // so, simple values such as "10px" are parsed to Float. + // complex values such as "rotate(1rad)" are returned as is. + result = jQuery.css( tween.elem, tween.prop, "" ); + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + // use step hook for back compat - use cssHook if its there - use .style if its + // available and use plain properties where available + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Remove in 2.0 - this supports IE8's panic based approach +// to setting things on disconnected nodes + +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.each([ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +}); + +jQuery.fn.extend({ + fadeTo: function( speed, to, easing, callback ) { + + // show any hidden elements after setting opacity to 0 + return this.filter( isHidden ).css( "opacity", 0 ).show() + + // animate to the value specified + .end().animate({ opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + doAnimation.finish = function() { + anim.stop( true ); + }; + // Empty animations, or finishing resolves immediately + if ( empty || jQuery._data( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each(function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = jQuery._data( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // start the next in the queue if the last step wasn't forced + // timers currently will call their complete callbacks, which will dequeue + // but only if they were gotoEnd + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + }); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each(function() { + var index, + data = jQuery._data( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // enable finishing flag on private data + data.finish = true; + + // empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.cur && hooks.cur.finish ) { + hooks.cur.finish.call( this ); + } + + // look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // turn off finishing flag + delete data.finish; + }); + } +}); + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + attrs = { height: type }, + i = 0; + + // if we include width, step value is 1 to do all cssExpand values, + // if we don't include width, step value is 2 to skip over Left and Right + includeWidth = includeWidth? 1 : 0; + for( ; i < 4 ; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show"), + slideUp: genFx("hide"), + slideToggle: genFx("toggle"), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +}); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; + + // normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p*Math.PI ) / 2; + } +}; + +jQuery.timers = []; +jQuery.fx = Tween.prototype.init; +jQuery.fx.tick = function() { + var timer, + timers = jQuery.timers, + i = 0; + + fxNow = jQuery.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + if ( timer() && jQuery.timers.push( timer ) ) { + jQuery.fx.start(); + } +}; + +jQuery.fx.interval = 13; + +jQuery.fx.start = function() { + if ( !timerId ) { + timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval ); + } +}; + +jQuery.fx.stop = function() { + clearInterval( timerId ); + timerId = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + // Default speed + _default: 400 +}; + +// Back Compat <1.8 extension point +jQuery.fx.step = {}; + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} +jQuery.fn.offset = function( options ) { + if ( arguments.length ) { + return options === undefined ? + this : + this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + var docElem, win, + box = { top: 0, left: 0 }, + elem = this[ 0 ], + doc = elem && elem.ownerDocument; + + if ( !doc ) { + return; + } + + docElem = doc.documentElement; + + // Make sure it's not a disconnected DOM node + if ( !jQuery.contains( docElem, elem ) ) { + return box; + } + + // If we don't have gBCR, just use 0,0 rather than error + // BlackBerry 5, iOS 3 (original iPhone) + if ( typeof elem.getBoundingClientRect !== core_strundefined ) { + box = elem.getBoundingClientRect(); + } + win = getWindow( doc ); + return { + top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ), + left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 ) + }; +}; + +jQuery.offset = { + + setOffset: function( elem, options, i ) { + var position = jQuery.css( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.css( elem, "top" ), + curCSSLeft = jQuery.css( elem, "left" ), + calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is either absolute or fixed + if ( calculatePosition ) { + curPosition = curElem.position(); + curTop = curPosition.top; + curLeft = curPosition.left; + } else { + curTop = parseFloat( curCSSTop ) || 0; + curLeft = parseFloat( curCSSLeft ) || 0; + } + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + if ( options.top != null ) { + props.top = ( options.top - curOffset.top ) + curTop; + } + if ( options.left != null ) { + props.left = ( options.left - curOffset.left ) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + + position: function() { + if ( !this[ 0 ] ) { + return; + } + + var offsetParent, offset, + parentOffset = { top: 0, left: 0 }, + elem = this[ 0 ]; + + // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent + if ( jQuery.css( elem, "position" ) === "fixed" ) { + // we assume that getBoundingClientRect is available when computed position is fixed + offset = elem.getBoundingClientRect(); + } else { + // Get *real* offsetParent + offsetParent = this.offsetParent(); + + // Get correct offsets + offset = this.offset(); + if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) { + parentOffset = offsetParent.offset(); + } + + // Add offsetParent borders + parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ); + parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true ); + } + + // Subtract parent offsets and element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + return { + top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), + left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true) + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.documentElement; + while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || document.documentElement; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) { + var top = /Y/.test( prop ); + + jQuery.fn[ method ] = function( val ) { + return jQuery.access( this, function( elem, method, val ) { + var win = getWindow( elem ); + + if ( val === undefined ) { + return win ? (prop in win) ? win[ prop ] : + win.document.documentElement[ method ] : + elem[ method ]; + } + + if ( win ) { + win.scrollTo( + !top ? val : jQuery( win ).scrollLeft(), + top ? val : jQuery( win ).scrollTop() + ); + + } else { + elem[ method ] = val; + } + }, method, val, arguments.length, null ); + }; +}); + +function getWindow( elem ) { + return jQuery.isWindow( elem ) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} +// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods +jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { + jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { + // margin is only for outerHeight, outerWidth + jQuery.fn[ funcName ] = function( margin, value ) { + var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), + extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); + + return jQuery.access( this, function( elem, type, value ) { + var doc; + + if ( jQuery.isWindow( elem ) ) { + // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there + // isn't a whole lot we can do. See pull request at this URL for discussion: + // https://github.com/jquery/jquery/pull/764 + return elem.document.documentElement[ "client" + name ]; + } + + // Get document width or height + if ( elem.nodeType === 9 ) { + doc = elem.documentElement; + + // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest + // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it. + return Math.max( + elem.body[ "scroll" + name ], doc[ "scroll" + name ], + elem.body[ "offset" + name ], doc[ "offset" + name ], + doc[ "client" + name ] + ); + } + + return value === undefined ? + // Get width or height on the element, requesting but not forcing parseFloat + jQuery.css( elem, type, extra ) : + + // Set width or height on the element + jQuery.style( elem, type, value, extra ); + }, type, chainable ? margin : undefined, chainable, null ); + }; + }); +}); +// Limit scope pollution from any deprecated API +// (function() { + +// })(); +// Expose jQuery to the global object +window.jQuery = window.$ = jQuery; + +// Expose jQuery as an AMD module, but only for AMD loaders that +// understand the issues with loading multiple versions of jQuery +// in a page that all might call define(). The loader will indicate +// they have special allowances for multiple jQuery versions by +// specifying define.amd.jQuery = true. Register as a named module, +// since jQuery can be concatenated with other files that may use define, +// but not use a proper concatenation script that understands anonymous +// AMD modules. A named AMD is safest and most robust way to register. +// Lowercase jquery is used because AMD module names are derived from +// file names, and jQuery is normally delivered in a lowercase file name. +// Do this after creating the global so that if an AMD module wants to call +// noConflict to hide this version of jQuery, it will work. +if ( typeof define === "function" && define.amd && define.amd.jQuery ) { + define( "jquery", [], function () { return jQuery; } ); +} + +})( window ); \ No newline at end of file diff --git a/js/mediaelement/build/loading.gif b/js/mediaelement/build/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..612222be5e474c36c345042dd6f697fa1d16a6a0 Binary files /dev/null and b/js/mediaelement/build/loading.gif differ diff --git a/js/mediaelement/build/mediaelement-and-player.js b/js/mediaelement/build/mediaelement-and-player.js new file mode 100644 index 0000000000000000000000000000000000000000..bbdd6766f4bb2a766a8b7326eca70bab2752fa67 --- /dev/null +++ b/js/mediaelement/build/mediaelement-and-player.js @@ -0,0 +1,5291 @@ +/*! +* MediaElement.js +* HTML5 <video> and <audio> shim and player +* http://mediaelementjs.com/ +* +* Creates a JavaScript object that mimics HTML5 MediaElement API +* for browsers that don't understand HTML5 or can't play the provided codec +* Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3 +* +* Copyright 2010-2014, John Dyer (http://j.hn) +* License: MIT +* +*/ +// Namespace +var mejs = mejs || {}; + +// version number +mejs.version = '2.15.1'; + + +// player number (for missing, same id attr) +mejs.meIndex = 0; + +// media types accepted by plugins +mejs.plugins = { + silverlight: [ + {version: [3,0], types: ['video/mp4','video/m4v','video/mov','video/wmv','audio/wma','audio/m4a','audio/mp3','audio/wav','audio/mpeg']} + ], + flash: [ + {version: [9,0,124], types: ['video/mp4','video/m4v','video/mov','video/flv','video/rtmp','video/x-flv','audio/flv','audio/x-flv','audio/mp3','audio/m4a','audio/mpeg', 'video/youtube', 'video/x-youtube', 'application/x-mpegURL']} + //,{version: [12,0], types: ['video/webm']} // for future reference (hopefully!) + ], + youtube: [ + {version: null, types: ['video/youtube', 'video/x-youtube', 'audio/youtube', 'audio/x-youtube']} + ], + vimeo: [ + {version: null, types: ['video/vimeo', 'video/x-vimeo']} + ] +}; + +/* +Utility methods +*/ +mejs.Utility = { + encodeUrl: function(url) { + return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26'); + }, + escapeHTML: function(s) { + return s.toString().split('&').join('&').split('<').join('<').split('"').join('"'); + }, + absolutizeUrl: function(url) { + var el = document.createElement('div'); + el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>'; + return el.firstChild.href; + }, + getScriptPath: function(scriptNames) { + var + i = 0, + j, + codePath = '', + testname = '', + slashPos, + filenamePos, + scriptUrl, + scriptPath, + scriptFilename, + scripts = document.getElementsByTagName('script'), + il = scripts.length, + jl = scriptNames.length; + + // go through all <script> tags + for (; i < il; i++) { + scriptUrl = scripts[i].src; + slashPos = scriptUrl.lastIndexOf('/'); + if (slashPos > -1) { + scriptFilename = scriptUrl.substring(slashPos + 1); + scriptPath = scriptUrl.substring(0, slashPos + 1); + } else { + scriptFilename = scriptUrl; + scriptPath = ''; + } + + // see if any <script> tags have a file name that matches the + for (j = 0; j < jl; j++) { + testname = scriptNames[j]; + filenamePos = scriptFilename.indexOf(testname); + if (filenamePos > -1) { + codePath = scriptPath; + break; + } + } + + // if we found a path, then break and return it + if (codePath !== '') { + break; + } + } + + // send the best path back + return codePath; + }, + secondsToTimeCode: function(time, forceHours, showFrameCount, fps) { + //add framecount + if (typeof showFrameCount == 'undefined') { + showFrameCount=false; + } else if(typeof fps == 'undefined') { + fps = 25; + } + + var hours = Math.floor(time / 3600) % 24, + minutes = Math.floor(time / 60) % 60, + seconds = Math.floor(time % 60), + frames = Math.floor(((time % 1)*fps).toFixed(3)), + result = + ( (forceHours || hours > 0) ? (hours < 10 ? '0' + hours : hours) + ':' : '') + + (minutes < 10 ? '0' + minutes : minutes) + ':' + + (seconds < 10 ? '0' + seconds : seconds) + + ((showFrameCount) ? ':' + (frames < 10 ? '0' + frames : frames) : ''); + + return result; + }, + + timeCodeToSeconds: function(hh_mm_ss_ff, forceHours, showFrameCount, fps){ + if (typeof showFrameCount == 'undefined') { + showFrameCount=false; + } else if(typeof fps == 'undefined') { + fps = 25; + } + + var tc_array = hh_mm_ss_ff.split(":"), + tc_hh = parseInt(tc_array[0], 10), + tc_mm = parseInt(tc_array[1], 10), + tc_ss = parseInt(tc_array[2], 10), + tc_ff = 0, + tc_in_seconds = 0; + + if (showFrameCount) { + tc_ff = parseInt(tc_array[3])/fps; + } + + tc_in_seconds = ( tc_hh * 3600 ) + ( tc_mm * 60 ) + tc_ss + tc_ff; + + return tc_in_seconds; + }, + + + convertSMPTEtoSeconds: function (SMPTE) { + if (typeof SMPTE != 'string') + return false; + + SMPTE = SMPTE.replace(',', '.'); + + var secs = 0, + decimalLen = (SMPTE.indexOf('.') != -1) ? SMPTE.split('.')[1].length : 0, + multiplier = 1; + + SMPTE = SMPTE.split(':').reverse(); + + for (var i = 0; i < SMPTE.length; i++) { + multiplier = 1; + if (i > 0) { + multiplier = Math.pow(60, i); + } + secs += Number(SMPTE[i]) * multiplier; + } + return Number(secs.toFixed(decimalLen)); + }, + + /* borrowed from SWFObject: http://code.google.com/p/swfobject/source/browse/trunk/swfobject/src/swfobject.js#474 */ + removeSwf: function(id) { + var obj = document.getElementById(id); + if (obj && /object|embed/i.test(obj.nodeName)) { + if (mejs.MediaFeatures.isIE) { + obj.style.display = "none"; + (function(){ + if (obj.readyState == 4) { + mejs.Utility.removeObjectInIE(id); + } else { + setTimeout(arguments.callee, 10); + } + })(); + } else { + obj.parentNode.removeChild(obj); + } + } + }, + removeObjectInIE: function(id) { + var obj = document.getElementById(id); + if (obj) { + for (var i in obj) { + if (typeof obj[i] == "function") { + obj[i] = null; + } + } + obj.parentNode.removeChild(obj); + } + } +}; + + +// Core detector, plugins are added below +mejs.PluginDetector = { + + // main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]); + hasPluginVersion: function(plugin, v) { + var pv = this.plugins[plugin]; + v[1] = v[1] || 0; + v[2] = v[2] || 0; + return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; + }, + + // cached values + nav: window.navigator, + ua: window.navigator.userAgent.toLowerCase(), + + // stored version numbers + plugins: [], + + // runs detectPlugin() and stores the version number + addPlugin: function(p, pluginName, mimeType, activeX, axDetect) { + this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect); + }, + + // get the version number from the mimetype (all but IE) or ActiveX (IE) + detectPlugin: function(pluginName, mimeType, activeX, axDetect) { + + var version = [0,0,0], + description, + i, + ax; + + // Firefox, Webkit, Opera + if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') { + description = this.nav.plugins[pluginName].description; + if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) { + version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.'); + for (i=0; i<version.length; i++) { + version[i] = parseInt(version[i].match(/\d+/), 10); + } + } + // Internet Explorer / ActiveX + } else if (typeof(window.ActiveXObject) != 'undefined') { + try { + ax = new ActiveXObject(activeX); + if (ax) { + version = axDetect(ax); + } + } + catch (e) { } + } + return version; + } +}; + +// Add Flash detection +mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) { + // adapted from SWFObject + var version = [], + d = ax.GetVariable("$version"); + if (d) { + d = d.split(" ")[1].split(","); + version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + return version; +}); + +// Add Silverlight detection +mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) { + // Silverlight cannot report its version number to IE + // but it does have a isVersionSupported function, so we have to loop through it to get a version number. + // adapted from http://www.silverlightversion.com/ + var v = [0,0,0,0], + loopMatch = function(ax, v, i, n) { + while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){ + v[i]+=n; + } + v[i] -= n; + }; + loopMatch(ax, v, 0, 1); + loopMatch(ax, v, 1, 1); + loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx) + loopMatch(ax, v, 2, 1000); + loopMatch(ax, v, 2, 100); + loopMatch(ax, v, 2, 10); + loopMatch(ax, v, 2, 1); + loopMatch(ax, v, 3, 1); + + return v; +}); +// add adobe acrobat +/* +PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) { + var version = [], + d = ax.GetVersions().split(',')[0].split('=')[1].split('.'); + + if (d) { + version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + return version; +}); +*/ +// necessary detection (fixes for <IE9) +mejs.MediaFeatures = { + init: function() { + var + t = this, + d = document, + nav = mejs.PluginDetector.nav, + ua = mejs.PluginDetector.ua.toLowerCase(), + i, + v, + html5Elements = ['source','track','audio','video']; + + // detect browsers (only the ones that have some kind of quirk we need to work around) + t.isiPad = (ua.match(/ipad/i) !== null); + t.isiPhone = (ua.match(/iphone/i) !== null); + t.isiOS = t.isiPhone || t.isiPad; + t.isAndroid = (ua.match(/android/i) !== null); + t.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null); + t.isBustedNativeHTTPS = (location.protocol === 'https:' && (ua.match(/android [12]\./) !== null || ua.match(/macintosh.* version.* safari/) !== null)); + t.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1 || nav.appName.toLowerCase().match(/trident/gi) !== null); + t.isChrome = (ua.match(/chrome/gi) !== null); + t.isChromium = (ua.match(/chromium/gi) !== null); + t.isFirefox = (ua.match(/firefox/gi) !== null); + t.isWebkit = (ua.match(/webkit/gi) !== null); + t.isGecko = (ua.match(/gecko/gi) !== null) && !t.isWebkit && !t.isIE; + t.isOpera = (ua.match(/opera/gi) !== null); + t.hasTouch = ('ontouchstart' in window); // && window.ontouchstart != null); // this breaks iOS 7 + + // borrowed from Modernizr + t.svg = !! document.createElementNS && + !! document.createElementNS('http://www.w3.org/2000/svg','svg').createSVGRect; + + // create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection + for (i=0; i<html5Elements.length; i++) { + v = document.createElement(html5Elements[i]); + } + + t.supportsMediaTag = (typeof v.canPlayType !== 'undefined' || t.isBustedAndroid); + + // Fix for IE9 on Windows 7N / Windows 7KN (Media Player not installer) + try{ + v.canPlayType("video/mp4"); + }catch(e){ + t.supportsMediaTag = false; + } + + // detect native JavaScript fullscreen (Safari/Firefox only, Chrome still fails) + + // iOS + t.hasSemiNativeFullScreen = (typeof v.webkitEnterFullscreen !== 'undefined'); + + // W3C + t.hasNativeFullscreen = (typeof v.requestFullscreen !== 'undefined'); + + // webkit/firefox/IE11+ + t.hasWebkitNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined'); + t.hasMozNativeFullScreen = (typeof v.mozRequestFullScreen !== 'undefined'); + t.hasMsNativeFullScreen = (typeof v.msRequestFullscreen !== 'undefined'); + + t.hasTrueNativeFullScreen = (t.hasWebkitNativeFullScreen || t.hasMozNativeFullScreen || t.hasMsNativeFullScreen); + t.nativeFullScreenEnabled = t.hasTrueNativeFullScreen; + + // Enabled? + if (t.hasMozNativeFullScreen) { + t.nativeFullScreenEnabled = document.mozFullScreenEnabled; + } else if (t.hasMsNativeFullScreen) { + t.nativeFullScreenEnabled = document.msFullscreenEnabled; + } + + if (t.isChrome) { + t.hasSemiNativeFullScreen = false; + } + + if (t.hasTrueNativeFullScreen) { + + t.fullScreenEventName = ''; + if (t.hasWebkitNativeFullScreen) { + t.fullScreenEventName = 'webkitfullscreenchange'; + + } else if (t.hasMozNativeFullScreen) { + t.fullScreenEventName = 'mozfullscreenchange'; + + } else if (t.hasMsNativeFullScreen) { + t.fullScreenEventName = 'MSFullscreenChange'; + } + + t.isFullScreen = function() { + if (t.hasMozNativeFullScreen) { + return d.mozFullScreen; + + } else if (t.hasWebkitNativeFullScreen) { + return d.webkitIsFullScreen; + + } else if (t.hasMsNativeFullScreen) { + return d.msFullscreenElement !== null; + } + } + + t.requestFullScreen = function(el) { + + if (t.hasWebkitNativeFullScreen) { + el.webkitRequestFullScreen(); + + } else if (t.hasMozNativeFullScreen) { + el.mozRequestFullScreen(); + + } else if (t.hasMsNativeFullScreen) { + el.msRequestFullscreen(); + + } + } + + t.cancelFullScreen = function() { + if (t.hasWebkitNativeFullScreen) { + document.webkitCancelFullScreen(); + + } else if (t.hasMozNativeFullScreen) { + document.mozCancelFullScreen(); + + } else if (t.hasMsNativeFullScreen) { + document.msExitFullscreen(); + + } + } + + } + + + // OS X 10.5 can't do this even if it says it can :( + if (t.hasSemiNativeFullScreen && ua.match(/mac os x 10_5/i)) { + t.hasNativeFullScreen = false; + t.hasSemiNativeFullScreen = false; + } + + } +}; +mejs.MediaFeatures.init(); + +/* +extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below) +*/ +mejs.HtmlMediaElement = { + pluginType: 'native', + isFullScreen: false, + + setCurrentTime: function (time) { + this.currentTime = time; + }, + + setMuted: function (muted) { + this.muted = muted; + }, + + setVolume: function (volume) { + this.volume = volume; + }, + + // for parity with the plugin versions + stop: function () { + this.pause(); + }, + + // This can be a url string + // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] + setSrc: function (url) { + + // Fix for IE9 which can't set .src when there are <source> elements. Awesome, right? + var + existingSources = this.getElementsByTagName('source'); + while (existingSources.length > 0){ + this.removeChild(existingSources[0]); + } + + if (typeof url == 'string') { + this.src = url; + } else { + var i, media; + + for (i=0; i<url.length; i++) { + media = url[i]; + if (this.canPlayType(media.type)) { + this.src = media.src; + break; + } + } + } + }, + + setVideoSize: function (width, height) { + this.width = width; + this.height = height; + } +}; + +/* +Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember] +*/ +mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) { + this.id = pluginid; + this.pluginType = pluginType; + this.src = mediaUrl; + this.events = {}; + this.attributes = {}; +}; + +// JavaScript values and ExternalInterface methods that match HTML5 video properties methods +// http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html +// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html +mejs.PluginMediaElement.prototype = { + + // special + pluginElement: null, + pluginType: '', + isFullScreen: false, + + // not implemented :( + playbackRate: -1, + defaultPlaybackRate: -1, + seekable: [], + played: [], + + // HTML5 read-only properties + paused: true, + ended: false, + seeking: false, + duration: 0, + error: null, + tagName: '', + + // HTML5 get/set properties, but only set (updated by event handlers) + muted: false, + volume: 1, + currentTime: 0, + + // HTML5 methods + play: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.playVideo(); + } else { + this.pluginApi.playMedia(); + } + this.paused = false; + } + }, + load: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + } else { + this.pluginApi.loadMedia(); + } + + this.paused = false; + } + }, + pause: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.pauseVideo(); + } else { + this.pluginApi.pauseMedia(); + } + + + this.paused = true; + } + }, + stop: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.stopVideo(); + } else { + this.pluginApi.stopMedia(); + } + this.paused = true; + } + }, + canPlayType: function(type) { + var i, + j, + pluginInfo, + pluginVersions = mejs.plugins[this.pluginType]; + + for (i=0; i<pluginVersions.length; i++) { + pluginInfo = pluginVersions[i]; + + // test if user has the correct plugin version + if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) { + + // test for plugin playback types + for (j=0; j<pluginInfo.types.length; j++) { + // find plugin that can play the type + if (type == pluginInfo.types[j]) { + return 'probably'; + } + } + } + } + + return ''; + }, + + positionFullscreenButton: function(x,y,visibleAndAbove) { + if (this.pluginApi != null && this.pluginApi.positionFullscreenButton) { + this.pluginApi.positionFullscreenButton(Math.floor(x),Math.floor(y),visibleAndAbove); + } + }, + + hideFullscreenButton: function() { + if (this.pluginApi != null && this.pluginApi.hideFullscreenButton) { + this.pluginApi.hideFullscreenButton(); + } + }, + + + // custom methods since not all JavaScript implementations support get/set + + // This can be a url string + // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] + setSrc: function (url) { + if (typeof url == 'string') { + this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url)); + this.src = mejs.Utility.absolutizeUrl(url); + } else { + var i, media; + + for (i=0; i<url.length; i++) { + media = url[i]; + if (this.canPlayType(media.type)) { + this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src)); + this.src = mejs.Utility.absolutizeUrl(url); + break; + } + } + } + + }, + setCurrentTime: function (time) { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.seekTo(time); + } else { + this.pluginApi.setCurrentTime(time); + } + + + + this.currentTime = time; + } + }, + setVolume: function (volume) { + if (this.pluginApi != null) { + // same on YouTube and MEjs + if (this.pluginType == 'youtube') { + this.pluginApi.setVolume(volume * 100); + } else { + this.pluginApi.setVolume(volume); + } + this.volume = volume; + } + }, + setMuted: function (muted) { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube') { + if (muted) { + this.pluginApi.mute(); + } else { + this.pluginApi.unMute(); + } + this.muted = muted; + this.dispatchEvent('volumechange'); + } else { + this.pluginApi.setMuted(muted); + } + this.muted = muted; + } + }, + + // additional non-HTML5 methods + setVideoSize: function (width, height) { + + //if (this.pluginType == 'flash' || this.pluginType == 'silverlight') { + if (this.pluginElement && this.pluginElement.style) { + this.pluginElement.style.width = width + 'px'; + this.pluginElement.style.height = height + 'px'; + } + if (this.pluginApi != null && this.pluginApi.setVideoSize) { + this.pluginApi.setVideoSize(width, height); + } + //} + }, + + setFullscreen: function (fullscreen) { + if (this.pluginApi != null && this.pluginApi.setFullscreen) { + this.pluginApi.setFullscreen(fullscreen); + } + }, + + enterFullScreen: function() { + if (this.pluginApi != null && this.pluginApi.setFullscreen) { + this.setFullscreen(true); + } + + }, + + exitFullScreen: function() { + if (this.pluginApi != null && this.pluginApi.setFullscreen) { + this.setFullscreen(false); + } + }, + + // start: fake events + addEventListener: function (eventName, callback, bubble) { + this.events[eventName] = this.events[eventName] || []; + this.events[eventName].push(callback); + }, + removeEventListener: function (eventName, callback) { + if (!eventName) { this.events = {}; return true; } + var callbacks = this.events[eventName]; + if (!callbacks) return true; + if (!callback) { this.events[eventName] = []; return true; } + for (var i = 0; i < callbacks.length; i++) { + if (callbacks[i] === callback) { + this.events[eventName].splice(i, 1); + return true; + } + } + return false; + }, + dispatchEvent: function (eventName) { + var i, + args, + callbacks = this.events[eventName]; + + if (callbacks) { + args = Array.prototype.slice.call(arguments, 1); + for (i = 0; i < callbacks.length; i++) { + callbacks[i].apply(null, args); + } + } + }, + // end: fake events + + // fake DOM attribute methods + hasAttribute: function(name){ + return (name in this.attributes); + }, + removeAttribute: function(name){ + delete this.attributes[name]; + }, + getAttribute: function(name){ + if (this.hasAttribute(name)) { + return this.attributes[name]; + } + return ''; + }, + setAttribute: function(name, value){ + this.attributes[name] = value; + }, + + remove: function() { + mejs.Utility.removeSwf(this.pluginElement.id); + mejs.MediaPluginBridge.unregisterPluginElement(this.pluginElement.id); + } +}; + +// Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties +mejs.MediaPluginBridge = { + + pluginMediaElements:{}, + htmlMediaElements:{}, + + registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) { + this.pluginMediaElements[id] = pluginMediaElement; + this.htmlMediaElements[id] = htmlMediaElement; + }, + + unregisterPluginElement: function (id) { + delete this.pluginMediaElements[id]; + delete this.htmlMediaElements[id]; + }, + + // when Flash/Silverlight is ready, it calls out to this method + initPlugin: function (id) { + + var pluginMediaElement = this.pluginMediaElements[id], + htmlMediaElement = this.htmlMediaElements[id]; + + if (pluginMediaElement) { + // find the javascript bridge + switch (pluginMediaElement.pluginType) { + case "flash": + pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id); + break; + case "silverlight": + pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id); + pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS; + break; + } + + if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) { + pluginMediaElement.success(pluginMediaElement, htmlMediaElement); + } + } + }, + + // receives events from Flash/Silverlight and sends them out as HTML5 media events + // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html + fireEvent: function (id, eventName, values) { + + var + e, + i, + bufferedTime, + pluginMediaElement = this.pluginMediaElements[id]; + + if(!pluginMediaElement){ + return; + } + + // fake event object to mimic real HTML media event. + e = { + type: eventName, + target: pluginMediaElement + }; + + // attach all values to element and event object + for (i in values) { + pluginMediaElement[i] = values[i]; + e[i] = values[i]; + } + + // fake the newer W3C buffered TimeRange (loaded and total have been removed) + bufferedTime = values.bufferedTime || 0; + + e.target.buffered = e.buffered = { + start: function(index) { + return 0; + }, + end: function (index) { + return bufferedTime; + }, + length: 1 + }; + + pluginMediaElement.dispatchEvent(e.type, e); + } +}; + +/* +Default options +*/ +mejs.MediaElementDefaults = { + // allows testing on HTML5, flash, silverlight + // auto: attempts to detect what the browser can do + // auto_plugin: prefer plugins and then attempt native HTML5 + // native: forces HTML5 playback + // shim: disallows HTML5, will attempt either Flash or Silverlight + // none: forces fallback view + mode: 'auto', + // remove or reorder to change plugin priority and availability + plugins: ['flash','silverlight','youtube','vimeo'], + // shows debug errors on screen + enablePluginDebug: false, + // use plugin for browsers that have trouble with Basic Authentication on HTTPS sites + httpsBasicAuthSite: false, + // overrides the type specified, useful for dynamic instantiation + type: '', + // path to Flash and Silverlight plugins + pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']), + // name of flash file + flashName: 'flashmediaelement.swf', + // streamer for RTMP streaming + flashStreamer: '', + // turns on the smoothing filter in Flash + enablePluginSmoothing: false, + // enabled pseudo-streaming (seek) on .mp4 files + enablePseudoStreaming: false, + // start query parameter sent to server for pseudo-streaming + pseudoStreamingStartQueryParam: 'start', + // name of silverlight file + silverlightName: 'silverlightmediaelement.xap', + // default if the <video width> is not specified + defaultVideoWidth: 480, + // default if the <video height> is not specified + defaultVideoHeight: 270, + // overrides <video width> + pluginWidth: -1, + // overrides <video height> + pluginHeight: -1, + // additional plugin variables in 'key=value' form + pluginVars: [], + // rate in milliseconds for Flash and Silverlight to fire the timeupdate event + // larger number is less accurate, but less strain on plugin->JavaScript bridge + timerRate: 250, + // initial volume for player + startVolume: 0.8, + success: function () { }, + error: function () { } +}; + +/* +Determines if a browser supports the <video> or <audio> element +and returns either the native element or a Flash/Silverlight version that +mimics HTML5 MediaElement +*/ +mejs.MediaElement = function (el, o) { + return mejs.HtmlMediaElementShim.create(el,o); +}; + +mejs.HtmlMediaElementShim = { + + create: function(el, o) { + var + options = mejs.MediaElementDefaults, + htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el, + tagName = htmlMediaElement.tagName.toLowerCase(), + isMediaTag = (tagName === 'audio' || tagName === 'video'), + src = (isMediaTag) ? htmlMediaElement.getAttribute('src') : htmlMediaElement.getAttribute('href'), + poster = htmlMediaElement.getAttribute('poster'), + autoplay = htmlMediaElement.getAttribute('autoplay'), + preload = htmlMediaElement.getAttribute('preload'), + controls = htmlMediaElement.getAttribute('controls'), + playback, + prop; + + // extend options + for (prop in o) { + options[prop] = o[prop]; + } + + // clean up attributes + src = (typeof src == 'undefined' || src === null || src == '') ? null : src; + poster = (typeof poster == 'undefined' || poster === null) ? '' : poster; + preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload; + autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false'); + controls = !(typeof controls == 'undefined' || controls === null || controls === 'false'); + + // test for HTML5 and plugin capabilities + playback = this.determinePlayback(htmlMediaElement, options, mejs.MediaFeatures.supportsMediaTag, isMediaTag, src); + playback.url = (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : ''; + + if (playback.method == 'native') { + // second fix for android + if (mejs.MediaFeatures.isBustedAndroid) { + htmlMediaElement.src = playback.url; + htmlMediaElement.addEventListener('click', function() { + htmlMediaElement.play(); + }, false); + } + + // add methods to native HTMLMediaElement + return this.updateNative(playback, options, autoplay, preload); + } else if (playback.method !== '') { + // create plugin to mimic HTMLMediaElement + + return this.createPlugin( playback, options, poster, autoplay, preload, controls); + } else { + // boo, no HTML5, no Flash, no Silverlight. + this.createErrorMessage( playback, options, poster ); + + return this; + } + }, + + determinePlayback: function(htmlMediaElement, options, supportsMediaTag, isMediaTag, src) { + var + mediaFiles = [], + i, + j, + k, + l, + n, + type, + result = { method: '', url: '', htmlMediaElement: htmlMediaElement, isVideo: (htmlMediaElement.tagName.toLowerCase() != 'audio')}, + pluginName, + pluginVersions, + pluginInfo, + dummy, + media; + + // STEP 1: Get URL and type from <video src> or <source src> + + // supplied type overrides <video type> and <source type> + if (typeof options.type != 'undefined' && options.type !== '') { + + // accept either string or array of types + if (typeof options.type == 'string') { + mediaFiles.push({type:options.type, url:src}); + } else { + + for (i=0; i<options.type.length; i++) { + mediaFiles.push({type:options.type[i], url:src}); + } + } + + // test for src attribute first + } else if (src !== null) { + type = this.formatType(src, htmlMediaElement.getAttribute('type')); + mediaFiles.push({type:type, url:src}); + + // then test for <source> elements + } else { + // test <source> types to see if they are usable + for (i = 0; i < htmlMediaElement.childNodes.length; i++) { + n = htmlMediaElement.childNodes[i]; + if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') { + src = n.getAttribute('src'); + type = this.formatType(src, n.getAttribute('type')); + media = n.getAttribute('media'); + + if (!media || !window.matchMedia || (window.matchMedia && window.matchMedia(media).matches)) { + mediaFiles.push({type:type, url:src}); + } + } + } + } + + // in the case of dynamicly created players + // check for audio types + if (!isMediaTag && mediaFiles.length > 0 && mediaFiles[0].url !== null && this.getTypeFromFile(mediaFiles[0].url).indexOf('audio') > -1) { + result.isVideo = false; + } + + + // STEP 2: Test for playback method + + // special case for Android which sadly doesn't implement the canPlayType function (always returns '') + if (mejs.MediaFeatures.isBustedAndroid) { + htmlMediaElement.canPlayType = function(type) { + return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : ''; + }; + } + + // special case for Chromium to specify natively supported video codecs (i.e. WebM and Theora) + if (mejs.MediaFeatures.isChromium) { + htmlMediaElement.canPlayType = function(type) { + return (type.match(/video\/(webm|ogv|ogg)/gi) !== null) ? 'maybe' : ''; + }; + } + + // test for native playback first + if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'native') && !(mejs.MediaFeatures.isBustedNativeHTTPS && options.httpsBasicAuthSite === true)) { + + if (!isMediaTag) { + + // create a real HTML5 Media Element + dummy = document.createElement( result.isVideo ? 'video' : 'audio'); + htmlMediaElement.parentNode.insertBefore(dummy, htmlMediaElement); + htmlMediaElement.style.display = 'none'; + + // use this one from now on + result.htmlMediaElement = htmlMediaElement = dummy; + } + + for (i=0; i<mediaFiles.length; i++) { + // normal check + if (mediaFiles[i].type == "video/m3u8" || htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '' + // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg') + || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '' + // special case for m4a supported by detecting mp4 support + || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/m4a/,'mp4')).replace(/no/, '') !== '') { + result.method = 'native'; + result.url = mediaFiles[i].url; + break; + } + } + + if (result.method === 'native') { + if (result.url !== null) { + htmlMediaElement.src = result.url; + } + + // if `auto_plugin` mode, then cache the native result but try plugins. + if (options.mode !== 'auto_plugin') { + return result; + } + } + } + + // if native playback didn't work, then test plugins + if (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'shim') { + for (i=0; i<mediaFiles.length; i++) { + type = mediaFiles[i].type; + + // test all plugins in order of preference [silverlight, flash] + for (j=0; j<options.plugins.length; j++) { + + pluginName = options.plugins[j]; + + // test version of plugin (for future features) + pluginVersions = mejs.plugins[pluginName]; + + for (k=0; k<pluginVersions.length; k++) { + pluginInfo = pluginVersions[k]; + + // test if user has the correct plugin version + + // for youtube/vimeo + if (pluginInfo.version == null || + + mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) { + + // test for plugin playback types + for (l=0; l<pluginInfo.types.length; l++) { + // find plugin that can play the type + if (type == pluginInfo.types[l]) { + result.method = pluginName; + result.url = mediaFiles[i].url; + return result; + } + } + } + } + } + } + } + + // at this point, being in 'auto_plugin' mode implies that we tried plugins but failed. + // if we have native support then return that. + if (options.mode === 'auto_plugin' && result.method === 'native') { + return result; + } + + // what if there's nothing to play? just grab the first available + if (result.method === '' && mediaFiles.length > 0) { + result.url = mediaFiles[0].url; + } + + return result; + }, + + formatType: function(url, type) { + var ext; + + // if no type is supplied, fake it with the extension + if (url && !type) { + return this.getTypeFromFile(url); + } else { + // only return the mime part of the type in case the attribute contains the codec + // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element + // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4` + + if (type && ~type.indexOf(';')) { + return type.substr(0, type.indexOf(';')); + } else { + return type; + } + } + }, + + getTypeFromFile: function(url) { + url = url.split('?')[0]; + var ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase(); + return (/(mp4|m4v|ogg|ogv|m3u8|webm|webmv|flv|wmv|mpeg|mov)/gi.test(ext) ? 'video' : 'audio') + '/' + this.getTypeFromExtension(ext); + }, + + getTypeFromExtension: function(ext) { + + switch (ext) { + case 'mp4': + case 'm4v': + case 'm4a': + return 'mp4'; + case 'webm': + case 'webma': + case 'webmv': + return 'webm'; + case 'ogg': + case 'oga': + case 'ogv': + return 'ogg'; + default: + return ext; + } + }, + + createErrorMessage: function(playback, options, poster) { + var + htmlMediaElement = playback.htmlMediaElement, + errorContainer = document.createElement('div'); + + errorContainer.className = 'me-cannotplay'; + + try { + errorContainer.style.width = htmlMediaElement.width + 'px'; + errorContainer.style.height = htmlMediaElement.height + 'px'; + } catch (e) {} + + if (options.customError) { + errorContainer.innerHTML = options.customError; + } else { + errorContainer.innerHTML = (poster !== '') ? + '<a href="' + playback.url + '"><img src="' + poster + '" width="100%" height="100%" /></a>' : + '<a href="' + playback.url + '"><span>' + mejs.i18n.t('Download File') + '</span></a>'; + } + + htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement); + htmlMediaElement.style.display = 'none'; + + options.error(htmlMediaElement); + }, + + createPlugin:function(playback, options, poster, autoplay, preload, controls) { + var + htmlMediaElement = playback.htmlMediaElement, + width = 1, + height = 1, + pluginid = 'me_' + playback.method + '_' + (mejs.meIndex++), + pluginMediaElement = new mejs.PluginMediaElement(pluginid, playback.method, playback.url), + container = document.createElement('div'), + specialIEContainer, + node, + initVars; + + // copy tagName from html media element + pluginMediaElement.tagName = htmlMediaElement.tagName + + // copy attributes from html media element to plugin media element + for (var i = 0; i < htmlMediaElement.attributes.length; i++) { + var attribute = htmlMediaElement.attributes[i]; + if (attribute.specified == true) { + pluginMediaElement.setAttribute(attribute.name, attribute.value); + } + } + + // check for placement inside a <p> tag (sometimes WYSIWYG editors do this) + node = htmlMediaElement.parentNode; + while (node !== null && node.tagName.toLowerCase() !== 'body' && node.parentNode != null) { + if (node.parentNode.tagName.toLowerCase() === 'p') { + node.parentNode.parentNode.insertBefore(node, node.parentNode); + break; + } + node = node.parentNode; + } + + if (playback.isVideo) { + width = (options.pluginWidth > 0) ? options.pluginWidth : (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth; + height = (options.pluginHeight > 0) ? options.pluginHeight : (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight; + + // in case of '%' make sure it's encoded + width = mejs.Utility.encodeUrl(width); + height = mejs.Utility.encodeUrl(height); + + } else { + if (options.enablePluginDebug) { + width = 320; + height = 240; + } + } + + // register plugin + pluginMediaElement.success = options.success; + mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement); + + // add container (must be added to DOM before inserting HTML for IE) + container.className = 'me-plugin'; + container.id = pluginid + '_container'; + + if (playback.isVideo) { + htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement); + } else { + document.body.insertBefore(container, document.body.childNodes[0]); + } + + // flash/silverlight vars + initVars = [ + 'id=' + pluginid, + 'isvideo=' + ((playback.isVideo) ? "true" : "false"), + 'autoplay=' + ((autoplay) ? "true" : "false"), + 'preload=' + preload, + 'width=' + width, + 'startvolume=' + options.startVolume, + 'timerrate=' + options.timerRate, + 'flashstreamer=' + options.flashStreamer, + 'height=' + height, + 'pseudostreamstart=' + options.pseudoStreamingStartQueryParam]; + + if (playback.url !== null) { + if (playback.method == 'flash') { + initVars.push('file=' + mejs.Utility.encodeUrl(playback.url)); + } else { + initVars.push('file=' + playback.url); + } + } + if (options.enablePluginDebug) { + initVars.push('debug=true'); + } + if (options.enablePluginSmoothing) { + initVars.push('smoothing=true'); + } + if (options.enablePseudoStreaming) { + initVars.push('pseudostreaming=true'); + } + if (controls) { + initVars.push('controls=true'); // shows controls in the plugin if desired + } + if (options.pluginVars) { + initVars = initVars.concat(options.pluginVars); + } + + switch (playback.method) { + case 'silverlight': + container.innerHTML = +'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' + +'<param name="initParams" value="' + initVars.join(',') + '" />' + +'<param name="windowless" value="true" />' + +'<param name="background" value="black" />' + +'<param name="minRuntimeVersion" value="3.0.0.0" />' + +'<param name="autoUpgrade" value="true" />' + +'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' + +'</object>'; + break; + + case 'flash': + + if (mejs.MediaFeatures.isIE) { + specialIEContainer = document.createElement('div'); + container.appendChild(specialIEContainer); + specialIEContainer.outerHTML = +'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + +'id="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' + +'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' + +'<param name="flashvars" value="' + initVars.join('&') + '" />' + +'<param name="quality" value="high" />' + +'<param name="bgcolor" value="#000000" />' + +'<param name="wmode" value="transparent" />' + +'<param name="allowScriptAccess" value="always" />' + +'<param name="allowFullScreen" value="true" />' + +'<param name="scale" value="default" />' + +'</object>'; + + } else { + + container.innerHTML = +'<embed id="' + pluginid + '" name="' + pluginid + '" ' + +'play="true" ' + +'loop="false" ' + +'quality="high" ' + +'bgcolor="#000000" ' + +'wmode="transparent" ' + +'allowScriptAccess="always" ' + +'allowFullScreen="true" ' + +'type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" ' + +'src="' + options.pluginPath + options.flashName + '" ' + +'flashvars="' + initVars.join('&') + '" ' + +'width="' + width + '" ' + +'height="' + height + '" ' + +'scale="default"' + +'class="mejs-shim"></embed>'; + } + break; + + case 'youtube': + + + var videoId; + // youtu.be url from share button + if (playback.url.lastIndexOf("youtu.be") != -1) { + videoId = playback.url.substr(playback.url.lastIndexOf('/')+1); + if (videoId.indexOf('?') != -1) { + videoId = videoId.substr(0, videoId.indexOf('?')); + } + } + else { + videoId = playback.url.substr(playback.url.lastIndexOf('=')+1); + } + youtubeSettings = { + container: container, + containerId: container.id, + pluginMediaElement: pluginMediaElement, + pluginId: pluginid, + videoId: videoId, + height: height, + width: width + }; + + if (mejs.PluginDetector.hasPluginVersion('flash', [10,0,0]) ) { + mejs.YouTubeApi.createFlash(youtubeSettings); + } else { + mejs.YouTubeApi.enqueueIframe(youtubeSettings); + } + + break; + + // DEMO Code. Does NOT work. + case 'vimeo': + var player_id = pluginid + "_player"; + pluginMediaElement.vimeoid = playback.url.substr(playback.url.lastIndexOf('/')+1); + + container.innerHTML ='<iframe src="//player.vimeo.com/video/' + pluginMediaElement.vimeoid + '?api=1&portrait=0&byline=0&title=0&player_id=' + player_id + '" width="' + width +'" height="' + height +'" frameborder="0" class="mejs-shim" id="' + player_id + '"></iframe>'; + if (typeof($f) == 'function') { // froogaloop available + var player = $f(container.childNodes[0]); + player.addEvent('ready', function() { + $.extend( player, { + playVideo: function() { + player.api( 'play' ); + }, + stopVideo: function() { + player.api( 'unload' ); + }, + pauseVideo: function() { + player.api( 'pause' ); + }, + seekTo: function( seconds ) { + player.api( 'seekTo', seconds ); + }, + setVolume: function( volume ) { + player.api( 'setVolume', volume ); + }, + setMuted: function( muted ) { + if( muted ) { + player.lastVolume = player.api( 'getVolume' ); + player.api( 'setVolume', 0 ); + } else { + player.api( 'setVolume', player.lastVolume ); + delete player.lastVolume; + } + } + }); + + function createEvent(player, pluginMediaElement, eventName, e) { + var obj = { + type: eventName, + target: pluginMediaElement + }; + if (eventName == 'timeupdate') { + pluginMediaElement.currentTime = obj.currentTime = e.seconds; + pluginMediaElement.duration = obj.duration = e.duration; + } + pluginMediaElement.dispatchEvent(obj.type, obj); + } + + player.addEvent('play', function() { + createEvent(player, pluginMediaElement, 'play'); + createEvent(player, pluginMediaElement, 'playing'); + }); + + player.addEvent('pause', function() { + createEvent(player, pluginMediaElement, 'pause'); + }); + + player.addEvent('finish', function() { + createEvent(player, pluginMediaElement, 'ended'); + }); + + player.addEvent('playProgress', function(e) { + createEvent(player, pluginMediaElement, 'timeupdate', e); + }); + + pluginMediaElement.pluginElement = container; + pluginMediaElement.pluginApi = player; + + // init mejs + mejs.MediaPluginBridge.initPlugin(pluginid); + }); + } + else { + console.warn("You need to include froogaloop for vimeo to work"); + } + break; + } + // hide original element + htmlMediaElement.style.display = 'none'; + // prevent browser from autoplaying when using a plugin + htmlMediaElement.removeAttribute('autoplay'); + + // FYI: options.success will be fired by the MediaPluginBridge + + return pluginMediaElement; + }, + + updateNative: function(playback, options, autoplay, preload) { + + var htmlMediaElement = playback.htmlMediaElement, + m; + + + // add methods to video object to bring it into parity with Flash Object + for (m in mejs.HtmlMediaElement) { + htmlMediaElement[m] = mejs.HtmlMediaElement[m]; + } + + /* + Chrome now supports preload="none" + if (mejs.MediaFeatures.isChrome) { + + // special case to enforce preload attribute (Chrome doesn't respect this) + if (preload === 'none' && !autoplay) { + + // forces the browser to stop loading (note: fails in IE9) + htmlMediaElement.src = ''; + htmlMediaElement.load(); + htmlMediaElement.canceledPreload = true; + + htmlMediaElement.addEventListener('play',function() { + if (htmlMediaElement.canceledPreload) { + htmlMediaElement.src = playback.url; + htmlMediaElement.load(); + htmlMediaElement.play(); + htmlMediaElement.canceledPreload = false; + } + }, false); + // for some reason Chrome forgets how to autoplay sometimes. + } else if (autoplay) { + htmlMediaElement.load(); + htmlMediaElement.play(); + } + } + */ + + // fire success code + options.success(htmlMediaElement, htmlMediaElement); + + return htmlMediaElement; + } +}; + +/* + - test on IE (object vs. embed) + - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE) + - fullscreen? +*/ + +// YouTube Flash and Iframe API +mejs.YouTubeApi = { + isIframeStarted: false, + isIframeLoaded: false, + loadIframeApi: function() { + if (!this.isIframeStarted) { + var tag = document.createElement('script'); + tag.src = "//www.youtube.com/player_api"; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + this.isIframeStarted = true; + } + }, + iframeQueue: [], + enqueueIframe: function(yt) { + + if (this.isLoaded) { + this.createIframe(yt); + } else { + this.loadIframeApi(); + this.iframeQueue.push(yt); + } + }, + createIframe: function(settings) { + + var + pluginMediaElement = settings.pluginMediaElement, + player = new YT.Player(settings.containerId, { + height: settings.height, + width: settings.width, + videoId: settings.videoId, + playerVars: {controls:0}, + events: { + 'onReady': function() { + + // hook up iframe object to MEjs + settings.pluginMediaElement.pluginApi = player; + + // init mejs + mejs.MediaPluginBridge.initPlugin(settings.pluginId); + + // create timer + setInterval(function() { + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); + }, 250); + }, + 'onStateChange': function(e) { + + mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement); + + } + } + }); + }, + + createEvent: function (player, pluginMediaElement, eventName) { + var obj = { + type: eventName, + target: pluginMediaElement + }; + + if (player && player.getDuration) { + + // time + pluginMediaElement.currentTime = obj.currentTime = player.getCurrentTime(); + pluginMediaElement.duration = obj.duration = player.getDuration(); + + // state + obj.paused = pluginMediaElement.paused; + obj.ended = pluginMediaElement.ended; + + // sound + obj.muted = player.isMuted(); + obj.volume = player.getVolume() / 100; + + // progress + obj.bytesTotal = player.getVideoBytesTotal(); + obj.bufferedBytes = player.getVideoBytesLoaded(); + + // fake the W3C buffered TimeRange + var bufferedTime = obj.bufferedBytes / obj.bytesTotal * obj.duration; + + obj.target.buffered = obj.buffered = { + start: function(index) { + return 0; + }, + end: function (index) { + return bufferedTime; + }, + length: 1 + }; + + } + + // send event up the chain + pluginMediaElement.dispatchEvent(obj.type, obj); + }, + + iFrameReady: function() { + + this.isLoaded = true; + this.isIframeLoaded = true; + + while (this.iframeQueue.length > 0) { + var settings = this.iframeQueue.pop(); + this.createIframe(settings); + } + }, + + // FLASH! + flashPlayers: {}, + createFlash: function(settings) { + + this.flashPlayers[settings.pluginId] = settings; + + /* + settings.container.innerHTML = + '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0" ' + + 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' + + '<param name="allowScriptAccess" value="always">' + + '<param name="wmode" value="transparent">' + + '</object>'; + */ + + var specialIEContainer, + youtubeUrl = '//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0'; + + if (mejs.MediaFeatures.isIE) { + + specialIEContainer = document.createElement('div'); + settings.container.appendChild(specialIEContainer); + specialIEContainer.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + +'id="' + settings.pluginId + '" width="' + settings.width + '" height="' + settings.height + '" class="mejs-shim">' + + '<param name="movie" value="' + youtubeUrl + '" />' + + '<param name="wmode" value="transparent" />' + + '<param name="allowScriptAccess" value="always" />' + + '<param name="allowFullScreen" value="true" />' + +'</object>'; + } else { + settings.container.innerHTML = + '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="' + youtubeUrl + '" ' + + 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' + + '<param name="allowScriptAccess" value="always">' + + '<param name="wmode" value="transparent">' + + '</object>'; + } + + }, + + flashReady: function(id) { + var + settings = this.flashPlayers[id], + player = document.getElementById(id), + pluginMediaElement = settings.pluginMediaElement; + + // hook up and return to MediaELementPlayer.success + pluginMediaElement.pluginApi = + pluginMediaElement.pluginElement = player; + mejs.MediaPluginBridge.initPlugin(id); + + // load the youtube video + player.cueVideoById(settings.videoId); + + var callbackName = settings.containerId + '_callback'; + + window[callbackName] = function(e) { + mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement); + } + + player.addEventListener('onStateChange', callbackName); + + setInterval(function() { + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); + }, 250); + + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'canplay'); + }, + + handleStateChange: function(youTubeState, player, pluginMediaElement) { + switch (youTubeState) { + case -1: // not started + pluginMediaElement.paused = true; + pluginMediaElement.ended = true; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata'); + //createYouTubeEvent(player, pluginMediaElement, 'loadeddata'); + break; + case 0: + pluginMediaElement.paused = false; + pluginMediaElement.ended = true; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended'); + break; + case 1: + pluginMediaElement.paused = false; + pluginMediaElement.ended = false; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play'); + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing'); + break; + case 2: + pluginMediaElement.paused = true; + pluginMediaElement.ended = false; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause'); + break; + case 3: // buffering + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress'); + break; + case 5: + // cued? + break; + + } + + } +} +// IFRAME +function onYouTubePlayerAPIReady() { + mejs.YouTubeApi.iFrameReady(); +} +// FLASH +function onYouTubePlayerReady(id) { + mejs.YouTubeApi.flashReady(id); +} + +window.mejs = mejs; +window.MediaElement = mejs.MediaElement; + +/*! + * Adds Internationalization and localization to mediaelement. + * + * This file does not contain translations, you have to add the manually. + * The schema is always the same: me-i18n-locale-[ISO_639-1 Code].js + * + * Examples are provided both for german and chinese translation. + * + * + * What is the concept beyond i18n? + * http://en.wikipedia.org/wiki/Internationalization_and_localization + * + * What langcode should i use? + * http://en.wikipedia.org/wiki/ISO_639-1 + * + * + * License? + * + * The i18n file uses methods from the Drupal project (drupal.js): + * - i18n.methods.t() (modified) + * - i18n.methods.checkPlain() (full copy) + * + * The Drupal project is (like mediaelementjs) licensed under GPLv2. + * - http://drupal.org/licensing/faq/#q1 + * - https://github.com/johndyer/mediaelement + * - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * + * @params + * - context - document, iframe .. + * - exports - CommonJS, window .. + * + */ +;(function(context, exports, undefined) { + "use strict"; + var i18n = { + "locale": { + "language" : '', + "strings" : {} + }, + "methods" : {} + }; +// start i18n + + + /** + * Get language, fallback to browser's language if empty + */ + i18n.getLanguage = function () { + var language = i18n.locale.language || window.navigator.userLanguage || window.navigator.language; + // convert to iso 639-1 (2-letters, lower case) + return language.substr(0, 2).toLowerCase(); + }; + + // i18n fixes for compatibility with WordPress + if ( typeof mejsL10n != 'undefined' ) { + i18n.locale.language = mejsL10n.language; + } + + + + /** + * Encode special characters in a plain-text string for display as HTML. + */ + i18n.methods.checkPlain = function (str) { + var character, regex, + replace = { + '&': '&', + '"': '"', + '<': '<', + '>': '>' + }; + str = String(str); + for (character in replace) { + if (replace.hasOwnProperty(character)) { + regex = new RegExp(character, 'g'); + str = str.replace(regex, replace[character]); + } + } + return str; + }; + + /** + * Translate strings to the page language or a given language. + * + * + * @param str + * A string containing the English string to translate. + * + * @param options + * - 'context' (defaults to the default context): The context the source string + * belongs to. + * + * @return + * The translated string, escaped via i18n.methods.checkPlain() + */ + i18n.methods.t = function (str, options) { + + // Fetch the localized version of the string. + if (i18n.locale.strings && i18n.locale.strings[options.context] && i18n.locale.strings[options.context][str]) { + str = i18n.locale.strings[options.context][str]; + } + + return i18n.methods.checkPlain(str); + }; + + + /** + * Wrapper for i18n.methods.t() + * + * @see i18n.methods.t() + * @throws InvalidArgumentException + */ + i18n.t = function(str, options) { + + if (typeof str === 'string' && str.length > 0) { + + // check every time due language can change for + // different reasons (translation, lang switcher ..) + var language = i18n.getLanguage(); + + options = options || { + "context" : language + }; + + return i18n.methods.t(str, options); + } + else { + throw { + "name" : 'InvalidArgumentException', + "message" : 'First argument is either not a string or empty.' + }; + } + }; + +// end i18n + exports.i18n = i18n; +}(document, mejs)); + +// i18n fixes for compatibility with WordPress +;(function(exports, undefined) { + + "use strict"; + + if ( typeof mejsL10n != 'undefined' ) { + exports[mejsL10n.language] = mejsL10n.strings; + } + +}(mejs.i18n.locale.strings)); + +/*! + * This is a i18n.locale language object. + * + * German translation by Tim Latz, latz.tim@gmail.com + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + if (typeof exports.de === 'undefined') { + exports.de = { + "Fullscreen" : "Vollbild", + "Go Fullscreen" : "Vollbild an", + "Turn off Fullscreen" : "Vollbild aus", + "Close" : "Schließen" + }; + } + +}(mejs.i18n.locale.strings)); +/*! + * This is a i18n.locale language object. + * + * Traditional chinese translation by Tim Latz, latz.tim@gmail.com + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + if (typeof exports.zh === 'undefined') { + exports.zh = { + "Fullscreen" : "全螢幕", + "Go Fullscreen" : "全屏模式", + "Turn off Fullscreen" : "退出全屏模式", + "Close" : "關閉" + }; + } + +}(mejs.i18n.locale.strings)); + + +/*! + * MediaElementPlayer + * http://mediaelementjs.com/ + * + * Creates a controller bar for HTML5 <video> add <audio> tags + * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) + * + * Copyright 2010-2013, John Dyer (http://j.hn/) + * License: MIT + * + */ +if (typeof jQuery != 'undefined') { + mejs.$ = jQuery; +} else if (typeof ender != 'undefined') { + mejs.$ = ender; +} +(function ($) { + + // default player values + mejs.MepDefaults = { + // url to poster (to fix iOS 3.x) + poster: '', + // When the video is ended, we can show the poster. + showPosterWhenEnded: false, + // default if the <video width> is not specified + defaultVideoWidth: 480, + // default if the <video height> is not specified + defaultVideoHeight: 270, + // if set, overrides <video width> + videoWidth: -1, + // if set, overrides <video height> + videoHeight: -1, + // default if the user doesn't specify + defaultAudioWidth: 400, + // default if the user doesn't specify + defaultAudioHeight: 30, + + // default amount to move back when back key is pressed + defaultSeekBackwardInterval: function(media) { + return (media.duration * 0.05); + }, + // default amount to move forward when forward key is pressed + defaultSeekForwardInterval: function(media) { + return (media.duration * 0.05); + }, + + // set dimensions via JS instead of CSS + setDimensions: true, + + // width of audio player + audioWidth: -1, + // height of audio player + audioHeight: -1, + // initial volume when the player starts (overrided by user cookie) + startVolume: 0.8, + // useful for <audio> player loops + loop: false, + // rewind to beginning when media ends + autoRewind: true, + // resize to media dimensions + enableAutosize: true, + // forces the hour marker (##:00:00) + alwaysShowHours: false, + + // show framecount in timecode (##:00:00:00) + showTimecodeFrameCount: false, + // used when showTimecodeFrameCount is set to true + framesPerSecond: 25, + + // automatically calculate the width of the progress bar based on the sizes of other elements + autosizeProgress : true, + // Hide controls when playing and mouse is not over the video + alwaysShowControls: false, + // Display the video control + hideVideoControlsOnLoad: false, + // Enable click video element to toggle play/pause + clickToPlayPause: true, + // force iPad's native controls + iPadUseNativeControls: false, + // force iPhone's native controls + iPhoneUseNativeControls: false, + // force Android's native controls + AndroidUseNativeControls: false, + // features to show + features: ['playpause','current','progress','duration','tracks','volume','fullscreen'], + // only for dynamic + isVideo: true, + + // turns keyboard support on and off for this instance + enableKeyboard: true, + + // whenthis player starts, it will pause other players + pauseOtherPlayers: true, + + // array of keyboard actions such as play pause + keyActions: [ + { + keys: [ + 32, // SPACE + 179 // GOOGLE play/pause button + ], + action: function(player, media) { + if (media.paused || media.ended) { + player.play(); + } else { + player.pause(); + } + } + }, + { + keys: [38], // UP + action: function(player, media) { + player.container.find('.mejs-volume-slider').css('display','block'); + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + var newVolume = Math.min(media.volume + 0.1, 1); + media.setVolume(newVolume); + } + }, + { + keys: [40], // DOWN + action: function(player, media) { + player.container.find('.mejs-volume-slider').css('display','block'); + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + var newVolume = Math.max(media.volume - 0.1, 0); + media.setVolume(newVolume); + } + }, + { + keys: [ + 37, // LEFT + 227 // Google TV rewind + ], + action: function(player, media) { + if (!isNaN(media.duration) && media.duration > 0) { + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + // 5% + var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0); + media.setCurrentTime(newTime); + } + } + }, + { + keys: [ + 39, // RIGHT + 228 // Google TV forward + ], + action: function(player, media) { + if (!isNaN(media.duration) && media.duration > 0) { + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + // 5% + var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration); + media.setCurrentTime(newTime); + } + } + }, + { + keys: [70], // F + action: function(player, media) { + if (typeof player.enterFullScreen != 'undefined') { + if (player.isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + } + } + }, + { + keys: [77], // M + action: function(player, media) { + player.container.find('.mejs-volume-slider').css('display','block'); + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + if (player.media.muted) { + player.setMuted(false); + } else { + player.setMuted(true); + } + } + } + ] + }; + + mejs.mepIndex = 0; + + mejs.players = {}; + + // wraps a MediaElement object in player controls + mejs.MediaElementPlayer = function(node, o) { + // enforce object, even without "new" (via John Resig) + if ( !(this instanceof mejs.MediaElementPlayer) ) { + return new mejs.MediaElementPlayer(node, o); + } + + var t = this; + + // these will be reset after the MediaElement.success fires + t.$media = t.$node = $(node); + t.node = t.media = t.$media[0]; + + // check for existing player + if (typeof t.node.player != 'undefined') { + return t.node.player; + } else { + // attach player to DOM node for reference + t.node.player = t; + } + + + // try to get options from data-mejsoptions + if (typeof o == 'undefined') { + o = t.$node.data('mejsoptions'); + } + + // extend default options + t.options = $.extend({},mejs.MepDefaults,o); + + // unique ID + t.id = 'mep_' + mejs.mepIndex++; + + // add to player array (for focus events) + mejs.players[t.id] = t; + + // start up + t.init(); + + return t; + }; + + // actual player + mejs.MediaElementPlayer.prototype = { + + hasFocus: false, + + controlsAreVisible: true, + + init: function() { + + var + t = this, + mf = mejs.MediaFeatures, + // options for MediaElement (shim) + meOptions = $.extend(true, {}, t.options, { + success: function(media, domNode) { t.meReady(media, domNode); }, + error: function(e) { t.handleError(e);} + }), + tagName = t.media.tagName.toLowerCase(); + + t.isDynamic = (tagName !== 'audio' && tagName !== 'video'); + + if (t.isDynamic) { + // get video from src or href? + t.isVideo = t.options.isVideo; + } else { + t.isVideo = (tagName !== 'audio' && t.options.isVideo); + } + + // use native controls in iPad, iPhone, and Android + if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) { + + // add controls and stop + t.$media.attr('controls', 'controls'); + + // attempt to fix iOS 3 bug + //t.$media.removeAttr('poster'); + // no Issue found on iOS3 -ttroxell + + // override Apple's autoplay override for iPads + if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { + t.play(); + } + + } else if (mf.isAndroid && t.options.AndroidUseNativeControls) { + + // leave default player + + } else { + + // DESKTOP: use MediaElementPlayer controls + + // remove native controls + t.$media.removeAttr('controls'); + + // build container + t.container = + $('<div id="' + t.id + '" class="mejs-container ' + (mejs.MediaFeatures.svg ? 'svg' : 'no-svg') + '">'+ + '<div class="mejs-inner">'+ + '<div class="mejs-mediaelement"></div>'+ + '<div class="mejs-layers"></div>'+ + '<div class="mejs-controls"></div>'+ + '<div class="mejs-clear"></div>'+ + '</div>' + + '</div>') + .addClass(t.$media[0].className) + .insertBefore(t.$media); + + // add classes for user and content + t.container.addClass( + (mf.isAndroid ? 'mejs-android ' : '') + + (mf.isiOS ? 'mejs-ios ' : '') + + (mf.isiPad ? 'mejs-ipad ' : '') + + (mf.isiPhone ? 'mejs-iphone ' : '') + + (t.isVideo ? 'mejs-video ' : 'mejs-audio ') + ); + + + // move the <video/video> tag into the right spot + if (mf.isiOS) { + + // sadly, you can't move nodes in iOS, so we have to destroy and recreate it! + var $newMedia = t.$media.clone(); + + t.container.find('.mejs-mediaelement').append($newMedia); + + t.$media.remove(); + t.$node = t.$media = $newMedia; + t.node = t.media = $newMedia[0] + + } else { + + // normal way of moving it into place (doesn't work on iOS) + t.container.find('.mejs-mediaelement').append(t.$media); + } + + // find parts + t.controls = t.container.find('.mejs-controls'); + t.layers = t.container.find('.mejs-layers'); + + // determine the size + + /* size priority: + (1) videoWidth (forced), + (2) style="width;height;" + (3) width attribute, + (4) defaultVideoWidth (for unspecified cases) + */ + + var tagType = (t.isVideo ? 'video' : 'audio'), + capsTagName = tagType.substring(0,1).toUpperCase() + tagType.substring(1); + + + + if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) { + t.width = t.options[tagType + 'Width']; + } else if (t.media.style.width !== '' && t.media.style.width !== null) { + t.width = t.media.style.width; + } else if (t.media.getAttribute('width') !== null) { + t.width = t.$media.attr('width'); + } else { + t.width = t.options['default' + capsTagName + 'Width']; + } + + if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) { + t.height = t.options[tagType + 'Height']; + } else if (t.media.style.height !== '' && t.media.style.height !== null) { + t.height = t.media.style.height; + } else if (t.$media[0].getAttribute('height') !== null) { + t.height = t.$media.attr('height'); + } else { + t.height = t.options['default' + capsTagName + 'Height']; + } + + // set the size, while we wait for the plugins to load below + t.setPlayerSize(t.width, t.height); + + // create MediaElementShim + meOptions.pluginWidth = t.width; + meOptions.pluginHeight = t.height; + } + + // create MediaElement shim + mejs.MediaElement(t.$media[0], meOptions); + + if (typeof(t.container) != 'undefined' && t.controlsAreVisible){ + // controls are shown when loaded + t.container.trigger('controlsshown'); + } + }, + + showControls: function(doAnimation) { + var t = this; + + doAnimation = typeof doAnimation == 'undefined' || doAnimation; + + if (t.controlsAreVisible) + return; + + if (doAnimation) { + t.controls + .css('visibility','visible') + .stop(true, true).fadeIn(200, function() { + t.controlsAreVisible = true; + t.container.trigger('controlsshown'); + }); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control') + .css('visibility','visible') + .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); + + } else { + t.controls + .css('visibility','visible') + .css('display','block'); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control') + .css('visibility','visible') + .css('display','block'); + + t.controlsAreVisible = true; + t.container.trigger('controlsshown'); + } + + t.setControlsSize(); + + }, + + hideControls: function(doAnimation) { + var t = this; + + doAnimation = typeof doAnimation == 'undefined' || doAnimation; + + if (!t.controlsAreVisible || t.options.alwaysShowControls) + return; + + if (doAnimation) { + // fade out main controls + t.controls.stop(true, true).fadeOut(200, function() { + $(this) + .css('visibility','hidden') + .css('display','block'); + + t.controlsAreVisible = false; + t.container.trigger('controlshidden'); + }); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() { + $(this) + .css('visibility','hidden') + .css('display','block'); + }); + } else { + + // hide main controls + t.controls + .css('visibility','hidden') + .css('display','block'); + + // hide others + t.container.find('.mejs-control') + .css('visibility','hidden') + .css('display','block'); + + t.controlsAreVisible = false; + t.container.trigger('controlshidden'); + } + }, + + controlsTimer: null, + + startControlsTimer: function(timeout) { + + var t = this; + + timeout = typeof timeout != 'undefined' ? timeout : 1500; + + t.killControlsTimer('start'); + + t.controlsTimer = setTimeout(function() { + // + t.hideControls(); + t.killControlsTimer('hide'); + }, timeout); + }, + + killControlsTimer: function(src) { + + var t = this; + + if (t.controlsTimer !== null) { + clearTimeout(t.controlsTimer); + delete t.controlsTimer; + t.controlsTimer = null; + } + }, + + controlsEnabled: true, + + disableControls: function() { + var t= this; + + t.killControlsTimer(); + t.hideControls(false); + this.controlsEnabled = false; + }, + + enableControls: function() { + var t= this; + + t.showControls(false); + + t.controlsEnabled = true; + }, + + + // Sets up all controls and events + meReady: function(media, domNode) { + + + var t = this, + mf = mejs.MediaFeatures, + autoplayAttr = domNode.getAttribute('autoplay'), + autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'), + featureIndex, + feature; + + // make sure it can't create itself again if a plugin reloads + if (t.created) { + return; + } else { + t.created = true; + } + + t.media = media; + t.domNode = domNode; + + if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) { + + // two built in features + t.buildposter(t, t.controls, t.layers, t.media); + t.buildkeyboard(t, t.controls, t.layers, t.media); + t.buildoverlays(t, t.controls, t.layers, t.media); + + // grab for use by features + t.findTracks(); + + // add user-defined features/controls + for (featureIndex in t.options.features) { + feature = t.options.features[featureIndex]; + if (t['build' + feature]) { + try { + t['build' + feature](t, t.controls, t.layers, t.media); + } catch (e) { + // TODO: report control error + //throw e; + + + } + } + } + + t.container.trigger('controlsready'); + + // reset all layers and controls + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + + + // controls fade + if (t.isVideo) { + + if (mejs.MediaFeatures.hasTouch) { + + // for touch devices (iOS, Android) + // show/hide without animation on touch + + t.$media.bind('touchstart', function() { + + + // toggle controls + if (t.controlsAreVisible) { + t.hideControls(false); + } else { + if (t.controlsEnabled) { + t.showControls(false); + } + } + }); + + } else { + + // create callback here since it needs access to current + // MediaElement object + t.clickToPlayPauseCallback = function() { + // + + if (t.options.clickToPlayPause) { + if (t.media.paused) { + t.play(); + } else { + t.pause(); + } + } + }; + + // click to play/pause + t.media.addEventListener('click', t.clickToPlayPauseCallback, false); + + // show/hide controls + t.container + .bind('mouseenter mouseover', function () { + if (t.controlsEnabled) { + if (!t.options.alwaysShowControls ) { + t.killControlsTimer('enter'); + t.showControls(); + t.startControlsTimer(2500); + } + } + }) + .bind('mousemove', function() { + if (t.controlsEnabled) { + if (!t.controlsAreVisible) { + t.showControls(); + } + if (!t.options.alwaysShowControls) { + t.startControlsTimer(2500); + } + } + }) + .bind('mouseleave', function () { + if (t.controlsEnabled) { + if (!t.media.paused && !t.options.alwaysShowControls) { + t.startControlsTimer(1000); + } + } + }); + } + + if(t.options.hideVideoControlsOnLoad) { + t.hideControls(false); + } + + // check for autoplay + if (autoplay && !t.options.alwaysShowControls) { + t.hideControls(); + } + + // resizer + if (t.options.enableAutosize) { + t.media.addEventListener('loadedmetadata', function(e) { + // if the <video height> was not set and the options.videoHeight was not set + // then resize to the real dimensions + if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) { + t.setPlayerSize(e.target.videoWidth, e.target.videoHeight); + t.setControlsSize(); + t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight); + } + }, false); + } + } + + // EVENTS + + // FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them) + media.addEventListener('play', function() { + var playerIndex; + + // go through all other players + for (playerIndex in mejs.players) { + var p = mejs.players[playerIndex]; + if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) { + p.pause(); + } + p.hasFocus = false; + } + + t.hasFocus = true; + },false); + + + // ended for all + t.media.addEventListener('ended', function (e) { + if(t.options.autoRewind) { + try{ + t.media.setCurrentTime(0); + } catch (exp) { + + } + } + t.media.pause(); + + if (t.setProgressRail) { + t.setProgressRail(); + } + if (t.setCurrentRail) { + t.setCurrentRail(); + } + + if (t.options.loop) { + t.play(); + } else if (!t.options.alwaysShowControls && t.controlsEnabled) { + t.showControls(); + } + }, false); + + // resize on the first play + t.media.addEventListener('loadedmetadata', function(e) { + if (t.updateDuration) { + t.updateDuration(); + } + if (t.updateCurrent) { + t.updateCurrent(); + } + + if (!t.isFullScreen) { + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + } + }, false); + + + // webkit has trouble doing this without a delay + setTimeout(function () { + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + }, 50); + + // adjust controls whenever window sizes (used to be in fullscreen only) + t.globalBind('resize', function() { + + // don't resize for fullscreen mode + if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) { + t.setPlayerSize(t.width, t.height); + } + + // always adjust controls + t.setControlsSize(); + }); + + // TEMP: needs to be moved somewhere else + if (t.media.pluginType == 'youtube' && t.options.autoplay) { + //LOK-Soft: added t.options.autoplay to if -- I can only guess this is for hiding play button when autoplaying youtube, general hiding play button layer causes missing button on player load + t.container.find('.mejs-overlay-play').hide(); + } + } + + // force autoplay for HTML5 + if (autoplay && media.pluginType == 'native') { + t.play(); + } + + + if (t.options.success) { + + if (typeof t.options.success == 'string') { + window[t.options.success](t.media, t.domNode, t); + } else { + t.options.success(t.media, t.domNode, t); + } + } + }, + + handleError: function(e) { + var t = this; + + t.controls.hide(); + + // Tell user that the file cannot be played + if (t.options.error) { + t.options.error(e); + } + }, + + setPlayerSize: function(width,height) { + var t = this; + + if( !t.options.setDimensions ) { + return false; + } + + if (typeof width != 'undefined') { + t.width = width; + } + + if (typeof height != 'undefined') { + t.height = height; + } + + // detect 100% mode - use currentStyle for IE since css() doesn't return percentages + if (t.height.toString().indexOf('%') > 0 || t.$node.css('max-width') === '100%' || (t.$node[0].currentStyle && t.$node[0].currentStyle.maxWidth === '100%')) { + + // do we have the native dimensions yet? + var nativeWidth = (function() { + if (t.isVideo) { + if (t.media.videoWidth && t.media.videoWidth > 0) { + return t.media.videoWidth; + } else if (t.media.getAttribute('width') !== null) { + return t.media.getAttribute('width'); + } else { + return t.options.defaultVideoWidth; + } + } else { + return t.options.defaultAudioWidth; + } + })(); + + var nativeHeight = (function() { + if (t.isVideo) { + if (t.media.videoHeight && t.media.videoHeight > 0) { + return t.media.videoHeight; + } else if (t.media.getAttribute('height') !== null) { + return t.media.getAttribute('height'); + } else { + return t.options.defaultVideoHeight; + } + } else { + return t.options.defaultAudioHeight; + } + })(); + + var + parentWidth = t.container.parent().closest(':visible').width(), + parentHeight = t.container.parent().closest(':visible').height(), + newHeight = t.isVideo || !t.options.autosizeProgress ? parseInt(parentWidth * nativeHeight/nativeWidth, 10) : nativeHeight; + + // When we use percent, the newHeight can't be calculated so we get the container height + if(isNaN(newHeight) || ( parentHeight != 0 && newHeight > parentHeight )) { + newHeight = parentHeight; + } + + if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) { + parentWidth = $(window).width(); + newHeight = $(window).height(); + } + + if ( newHeight != 0 && parentWidth != 0 ) { + // set outer container size + t.container + .width(parentWidth) + .height(newHeight); + + // set native <video> or <audio> and shims + t.$media.add(t.container.find('.mejs-shim')) + .width('100%') + .height('100%'); + + // if shim is ready, send the size to the embeded plugin + if (t.isVideo) { + if (t.media.setVideoSize) { + t.media.setVideoSize(parentWidth, newHeight); + } + } + + // set the layers + t.layers.children('.mejs-layer') + .width('100%') + .height('100%'); + } + + + } else { + + t.container + .width(t.width) + .height(t.height); + + t.layers.children('.mejs-layer') + .width(t.width) + .height(t.height); + + } + + // special case for big play button so it doesn't go over the controls area + var playLayer = t.layers.find('.mejs-overlay-play'), + playButton = playLayer.find('.mejs-overlay-button'); + + playLayer.height(t.container.height() - t.controls.height()); + playButton.css('margin-top', '-' + (playButton.height()/2 - t.controls.height()/2).toString() + 'px' ); + + }, + + setControlsSize: function() { + var t = this, + usedWidth = 0, + railWidth = 0, + rail = t.controls.find('.mejs-time-rail'), + total = t.controls.find('.mejs-time-total'), + current = t.controls.find('.mejs-time-current'), + loaded = t.controls.find('.mejs-time-loaded'), + others = rail.siblings(), + lastControl = others.last(), + lastControlPosition = null; + + // skip calculation if hidden + if (!t.container.is(':visible') || !rail.length || !rail.is(':visible')) { + return; + } + + + // allow the size to come from custom CSS + if (t.options && !t.options.autosizeProgress) { + // Also, frontends devs can be more flexible + // due the opportunity of absolute positioning. + railWidth = parseInt(rail.css('width')); + } + + // attempt to autosize + if (railWidth === 0 || !railWidth) { + + // find the size of all the other controls besides the rail + others.each(function() { + var $this = $(this); + if ($this.css('position') != 'absolute' && $this.is(':visible')) { + usedWidth += $(this).outerWidth(true); + } + }); + + // fit the rail into the remaining space + railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width()); + } + + // resize the rail, + // but then check if the last control (say, the fullscreen button) got pushed down + // this often happens when zoomed + do { + // outer area + rail.width(railWidth); + // dark space + total.width(railWidth - (total.outerWidth(true) - total.width())); + + if (lastControl.css('position') != 'absolute') { + lastControlPosition = lastControl.position(); + railWidth--; + } + } while (lastControlPosition != null && lastControlPosition.top > 0 && railWidth > 0); + + if (t.setProgressRail) + t.setProgressRail(); + if (t.setCurrentRail) + t.setCurrentRail(); + }, + + + buildposter: function(player, controls, layers, media) { + var t = this, + poster = + $('<div class="mejs-poster mejs-layer">' + + '</div>') + .appendTo(layers), + posterUrl = player.$media.attr('poster'); + + // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) + if (player.options.poster !== '') { + posterUrl = player.options.poster; + } + + // second, try the real poster + if (posterUrl !== '' && posterUrl != null) { + t.setPoster(posterUrl); + } else { + poster.hide(); + } + + media.addEventListener('play',function() { + poster.hide(); + }, false); + + if(player.options.showPosterWhenEnded && player.options.autoRewind){ + media.addEventListener('ended',function() { + poster.show(); + }, false); + } + }, + + setPoster: function(url) { + var t = this, + posterDiv = t.container.find('.mejs-poster'), + posterImg = posterDiv.find('img'); + + if (posterImg.length == 0) { + posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv); + } + + posterImg.attr('src', url); + posterDiv.css({'background-image' : 'url(' + url + ')'}); + }, + + buildoverlays: function(player, controls, layers, media) { + var t = this; + if (!player.isVideo) + return; + + var + loading = + $('<div class="mejs-overlay mejs-layer">'+ + '<div class="mejs-overlay-loading"><span></span></div>'+ + '</div>') + .hide() // start out hidden + .appendTo(layers), + error = + $('<div class="mejs-overlay mejs-layer">'+ + '<div class="mejs-overlay-error"></div>'+ + '</div>') + .hide() // start out hidden + .appendTo(layers), + // this needs to come last so it's on top + bigPlay = + $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ + '<div class="mejs-overlay-button"></div>'+ + '</div>') + .appendTo(layers) + .bind('click', function() { // Removed 'touchstart' due issues on Samsung Android devices where a tap on bigPlay started and immediately stopped the video + if (t.options.clickToPlayPause) { + if (media.paused) { + media.play(); + } + } + }); + + /* + if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) { + bigPlay.remove(); + loading.remove(); + } + */ + + + // show/hide big play button + media.addEventListener('play',function() { + bigPlay.hide(); + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.hide(); + }, false); + + media.addEventListener('playing', function() { + bigPlay.hide(); + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.hide(); + }, false); + + media.addEventListener('seeking', function() { + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + + media.addEventListener('seeked', function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + }, false); + + media.addEventListener('pause',function() { + if (!mejs.MediaFeatures.isiPhone) { + bigPlay.show(); + } + }, false); + + media.addEventListener('waiting', function() { + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + + + // show/hide loading + media.addEventListener('loadeddata',function() { + // for some reason Chrome is firing this event + //if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') + // return; + + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + media.addEventListener('canplay',function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + }, false); + + // error handling + media.addEventListener('error',function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.show(); + error.find('mejs-overlay-error').html("Error loading this resource"); + }, false); + + media.addEventListener('keydown', function(e) { + t.onkeydown(player, media, e); + }, false); + }, + + buildkeyboard: function(player, controls, layers, media) { + + var t = this; + + // listen for key presses + t.globalBind('keydown', function(e) { + return t.onkeydown(player, media, e); + }); + + // check if someone clicked outside a player region, then kill its focus + t.globalBind('click', function(event) { + player.hasFocus = $(event.target).closest('.mejs-container').length != 0; + }); + + }, + onkeydown: function(player, media, e) { + if (player.hasFocus && player.options.enableKeyboard) { + // find a matching key + for (var i = 0, il = player.options.keyActions.length; i < il; i++) { + var keyAction = player.options.keyActions[i]; + + for (var j = 0, jl = keyAction.keys.length; j < jl; j++) { + if (e.keyCode == keyAction.keys[j]) { + if (typeof(e.preventDefault) == "function") e.preventDefault(); + keyAction.action(player, media, e.keyCode); + return false; + } + } + } + } + + return true; + }, + + findTracks: function() { + var t = this, + tracktags = t.$media.find('track'); + + // store for use by plugins + t.tracks = []; + tracktags.each(function(index, track) { + + track = $(track); + + t.tracks.push({ + srclang: (track.attr('srclang')) ? track.attr('srclang').toLowerCase() : '', + src: track.attr('src'), + kind: track.attr('kind'), + label: track.attr('label') || '', + entries: [], + isLoaded: false + }); + }); + }, + changeSkin: function(className) { + this.container[0].className = 'mejs-container ' + className; + this.setPlayerSize(this.width, this.height); + this.setControlsSize(); + }, + play: function() { + this.load(); + this.media.play(); + }, + pause: function() { + try { + this.media.pause(); + } catch (e) {} + }, + load: function() { + if (!this.isLoaded) { + this.media.load(); + } + + this.isLoaded = true; + }, + setMuted: function(muted) { + this.media.setMuted(muted); + }, + setCurrentTime: function(time) { + this.media.setCurrentTime(time); + }, + getCurrentTime: function() { + return this.media.currentTime; + }, + setVolume: function(volume) { + this.media.setVolume(volume); + }, + getVolume: function() { + return this.media.volume; + }, + setSrc: function(src) { + this.media.setSrc(src); + }, + remove: function() { + var t = this, featureIndex, feature; + + // invoke features cleanup + for (featureIndex in t.options.features) { + feature = t.options.features[featureIndex]; + if (t['clean' + feature]) { + try { + t['clean' + feature](t); + } catch (e) { + // TODO: report control error + //throw e; + // + // + } + } + } + + // grab video and put it back in place + if (!t.isDynamic) { + t.$media.prop('controls', true); + // detach events from the video + // TODO: detach event listeners better than this; + // also detach ONLY the events attached by this plugin! + t.$node.clone().insertBefore(t.container).show(); + t.$node.remove(); + } else { + t.$node.insertBefore(t.container); + } + + if (t.media.pluginType !== 'native') { + t.media.remove(); + } + + // Remove the player from the mejs.players object so that pauseOtherPlayers doesn't blow up when trying to pause a non existance flash api. + delete mejs.players[t.id]; + + if (typeof t.container == 'object') { + t.container.remove(); + } + t.globalUnbind(); + delete t.node.player; + } + }; + + (function(){ + var rwindow = /^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/; + + function splitEvents(events, id) { + // add player ID as an event namespace so it's easier to unbind them all later + var ret = {d: [], w: []}; + $.each((events || '').split(' '), function(k, v){ + var eventname = v + '.' + id; + if (eventname.indexOf('.') === 0) { + ret.d.push(eventname); + ret.w.push(eventname); + } + else { + ret[rwindow.test(v) ? 'w' : 'd'].push(eventname); + } + }); + ret.d = ret.d.join(' '); + ret.w = ret.w.join(' '); + return ret; + } + + mejs.MediaElementPlayer.prototype.globalBind = function(events, data, callback) { + var t = this; + events = splitEvents(events, t.id); + if (events.d) $(document).bind(events.d, data, callback); + if (events.w) $(window).bind(events.w, data, callback); + }; + + mejs.MediaElementPlayer.prototype.globalUnbind = function(events, callback) { + var t = this; + events = splitEvents(events, t.id); + if (events.d) $(document).unbind(events.d, callback); + if (events.w) $(window).unbind(events.w, callback); + }; + })(); + + // turn into jQuery plugin + if (typeof $ != 'undefined') { + $.fn.mediaelementplayer = function (options) { + if (options === false) { + this.each(function () { + var player = $(this).data('mediaelementplayer'); + if (player) { + player.remove(); + } + $(this).removeData('mediaelementplayer'); + }); + } + else { + this.each(function () { + $(this).data('mediaelementplayer', new mejs.MediaElementPlayer(this, options)); + }); + } + return this; + }; + + + $(document).ready(function() { + // auto enable using JSON attribute + $('.mejs-player').mediaelementplayer(); + }); + } + + // push out to window + window.MediaElementPlayer = mejs.MediaElementPlayer; + +})(mejs.$); + +(function($) { + + $.extend(mejs.MepDefaults, { + playpauseText: mejs.i18n.t('Play/Pause') + }); + + // PLAY/pause BUTTON + $.extend(MediaElementPlayer.prototype, { + buildplaypause: function(player, controls, layers, media) { + var + t = this, + play = + $('<div class="mejs-button mejs-playpause-button mejs-play" >' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.playpauseText + '" aria-label="' + t.options.playpauseText + '"></button>' + + '</div>') + .appendTo(controls) + .click(function(e) { + e.preventDefault(); + + if (media.paused) { + media.play(); + } else { + media.pause(); + } + + return false; + }); + + media.addEventListener('play',function() { + play.removeClass('mejs-play').addClass('mejs-pause'); + }, false); + media.addEventListener('playing',function() { + play.removeClass('mejs-play').addClass('mejs-pause'); + }, false); + + + media.addEventListener('pause',function() { + play.removeClass('mejs-pause').addClass('mejs-play'); + }, false); + media.addEventListener('paused',function() { + play.removeClass('mejs-pause').addClass('mejs-play'); + }, false); + } + }); + +})(mejs.$); + +(function($) { + + $.extend(mejs.MepDefaults, { + stopText: 'Stop' + }); + + // STOP BUTTON + $.extend(MediaElementPlayer.prototype, { + buildstop: function(player, controls, layers, media) { + var t = this, + stop = + $('<div class="mejs-button mejs-stop-button mejs-stop">' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '" aria-label="' + t.options.stopText + '"></button>' + + '</div>') + .appendTo(controls) + .click(function() { + if (!media.paused) { + media.pause(); + } + if (media.currentTime > 0) { + media.setCurrentTime(0); + media.pause(); + controls.find('.mejs-time-current').width('0px'); + controls.find('.mejs-time-handle').css('left', '0px'); + controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); + controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); + layers.find('.mejs-poster').show(); + } + }); + } + }); + +})(mejs.$); + +(function($) { + // progress/loaded bar + $.extend(MediaElementPlayer.prototype, { + buildprogress: function(player, controls, layers, media) { + + $('<div class="mejs-time-rail">'+ + '<span class="mejs-time-total">'+ + '<span class="mejs-time-buffering"></span>'+ + '<span class="mejs-time-loaded"></span>'+ + '<span class="mejs-time-current"></span>'+ + '<span class="mejs-time-handle"></span>'+ + '<span class="mejs-time-float">' + + '<span class="mejs-time-float-current">00:00</span>' + + '<span class="mejs-time-float-corner"></span>' + + '</span>'+ + '</span>'+ + '</div>') + .appendTo(controls); + controls.find('.mejs-time-buffering').hide(); + + var + t = this, + total = controls.find('.mejs-time-total'), + loaded = controls.find('.mejs-time-loaded'), + current = controls.find('.mejs-time-current'), + handle = controls.find('.mejs-time-handle'), + timefloat = controls.find('.mejs-time-float'), + timefloatcurrent = controls.find('.mejs-time-float-current'), + handleMouseMove = function (e) { + // mouse or touch position relative to the object + if (e.originalEvent.changedTouches) { + var x = e.originalEvent.changedTouches[0].pageX; + }else{ + var x = e.pageX; + } + + var offset = total.offset(), + width = total.outerWidth(true), + percentage = 0, + newTime = 0, + pos = 0; + + + if (media.duration) { + if (x < offset.left) { + x = offset.left; + } else if (x > width + offset.left) { + x = width + offset.left; + } + + pos = x - offset.left; + percentage = (pos / width); + newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; + + // seek to where the mouse is + if (mouseIsDown && newTime !== media.currentTime) { + media.setCurrentTime(newTime); + } + + // position floating time box + if (!mejs.MediaFeatures.hasTouch) { + timefloat.css('left', pos); + timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); + timefloat.show(); + } + } + }, + mouseIsDown = false, + mouseIsOver = false; + + // handle clicks + //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); + total + .bind('mousedown touchstart', function (e) { + // only handle left clicks or touch + if (e.which === 1 || e.which === 0) { + mouseIsDown = true; + handleMouseMove(e); + t.globalBind('mousemove.dur touchmove.dur', function(e) { + handleMouseMove(e); + }); + t.globalBind('mouseup.dur touchend.dur', function (e) { + mouseIsDown = false; + timefloat.hide(); + t.globalUnbind('.dur'); + }); + return false; + } + }) + .bind('mouseenter', function(e) { + mouseIsOver = true; + t.globalBind('mousemove.dur', function(e) { + handleMouseMove(e); + }); + if (!mejs.MediaFeatures.hasTouch) { + timefloat.show(); + } + }) + .bind('mouseleave',function(e) { + mouseIsOver = false; + if (!mouseIsDown) { + t.globalUnbind('.dur'); + timefloat.hide(); + } + }); + + // loading + media.addEventListener('progress', function (e) { + player.setProgressRail(e); + player.setCurrentRail(e); + }, false); + + // current time + media.addEventListener('timeupdate', function(e) { + player.setProgressRail(e); + player.setCurrentRail(e); + }, false); + + + // store for later use + t.loaded = loaded; + t.total = total; + t.current = current; + t.handle = handle; + }, + setProgressRail: function(e) { + + var + t = this, + target = (e != undefined) ? e.target : t.media, + percent = null; + + // newest HTML5 spec has buffered array (FF4, Webkit) + if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { + // TODO: account for a real array with multiple values (only Firefox 4 has this so far) + percent = target.buffered.end(0) / target.duration; + } + // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() + // to be anything other than 0. If the byte count is available we use this instead. + // Browsers that support the else if do not seem to have the bufferedBytes value and + // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. + else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) { + percent = target.bufferedBytes / target.bytesTotal; + } + // Firefox 3 with an Ogg file seems to go this way + else if (e && e.lengthComputable && e.total != 0) { + percent = e.loaded/e.total; + } + + // finally update the progress bar + if (percent !== null) { + percent = Math.min(1, Math.max(0, percent)); + // update loaded bar + if (t.loaded && t.total) { + t.loaded.width(t.total.width() * percent); + } + } + }, + setCurrentRail: function() { + + var t = this; + + if (t.media.currentTime != undefined && t.media.duration) { + + // update bar and handle + if (t.total && t.handle) { + var + newWidth = Math.round(t.total.width() * t.media.currentTime / t.media.duration), + handlePos = newWidth - Math.round(t.handle.outerWidth(true) / 2); + + t.current.width(newWidth); + t.handle.css('left', handlePos); + } + } + + } + }); +})(mejs.$); + +(function($) { + + // options + $.extend(mejs.MepDefaults, { + duration: -1, + timeAndDurationSeparator: '<span> | </span>' + }); + + + // current and duration 00:00 / 00:00 + $.extend(MediaElementPlayer.prototype, { + buildcurrent: function(player, controls, layers, media) { + var t = this; + + $('<div class="mejs-time">'+ + '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') + + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')+ '</span>'+ + '</div>') + .appendTo(controls); + + t.currenttime = t.controls.find('.mejs-currenttime'); + + media.addEventListener('timeupdate',function() { + player.updateCurrent(); + }, false); + }, + + + buildduration: function(player, controls, layers, media) { + var t = this; + + if (controls.children().last().find('.mejs-currenttime').length > 0) { + $(t.options.timeAndDurationSeparator + + '<span class="mejs-duration">' + + (t.options.duration > 0 ? + mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : + ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) + ) + + '</span>') + .appendTo(controls.find('.mejs-time')); + } else { + + // add class to current time + controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); + + $('<div class="mejs-time mejs-duration-container">'+ + '<span class="mejs-duration">' + + (t.options.duration > 0 ? + mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : + ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) + ) + + '</span>' + + '</div>') + .appendTo(controls); + } + + t.durationD = t.controls.find('.mejs-duration'); + + media.addEventListener('timeupdate',function() { + player.updateDuration(); + }, false); + }, + + updateCurrent: function() { + var t = this; + + if (t.currenttime) { + t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); + } + }, + + updateDuration: function() { + var t = this; + + //Toggle the long video class if the video is longer than an hour. + t.container.toggleClass("mejs-long-video", t.media.duration > 3600); + + if (t.durationD && (t.options.duration > 0 || t.media.duration)) { + t.durationD.html(mejs.Utility.secondsToTimeCode(t.options.duration > 0 ? t.options.duration : t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); + } + } + }); + +})(mejs.$); + +(function($) { + + $.extend(mejs.MepDefaults, { + muteText: mejs.i18n.t('Mute Toggle'), + hideVolumeOnTouchDevices: true, + + audioVolume: 'horizontal', + videoVolume: 'vertical' + }); + + $.extend(MediaElementPlayer.prototype, { + buildvolume: function(player, controls, layers, media) { + + // Android and iOS don't support volume controls + if ((mejs.MediaFeatures.isAndroid || mejs.MediaFeatures.isiOS) && this.options.hideVolumeOnTouchDevices) + return; + + var t = this, + mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume, + mute = (mode == 'horizontal') ? + + // horizontal version + $('<div class="mejs-button mejs-volume-button mejs-mute">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+ + '</div>' + + '<div class="mejs-horizontal-volume-slider">'+ // outer background + '<div class="mejs-horizontal-volume-total"></div>'+ // line background + '<div class="mejs-horizontal-volume-current"></div>'+ // current volume + '<div class="mejs-horizontal-volume-handle"></div>'+ // handle + '</div>' + ) + .appendTo(controls) : + + // vertical version + $('<div class="mejs-button mejs-volume-button mejs-mute">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+ + '<div class="mejs-volume-slider">'+ // outer background + '<div class="mejs-volume-total"></div>'+ // line background + '<div class="mejs-volume-current"></div>'+ // current volume + '<div class="mejs-volume-handle"></div>'+ // handle + '</div>'+ + '</div>') + .appendTo(controls), + volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'), + volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'), + volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'), + volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'), + + positionVolumeHandle = function(volume, secondTry) { + + if (!volumeSlider.is(':visible') && typeof secondTry == 'undefined') { + volumeSlider.show(); + positionVolumeHandle(volume, true); + volumeSlider.hide() + return; + } + + // correct to 0-1 + volume = Math.max(0,volume); + volume = Math.min(volume,1); + + // ajust mute button style + if (volume == 0) { + mute.removeClass('mejs-mute').addClass('mejs-unmute'); + } else { + mute.removeClass('mejs-unmute').addClass('mejs-mute'); + } + + // position slider + if (mode == 'vertical') { + var + + // height of the full size volume slider background + totalHeight = volumeTotal.height(), + + // top/left of full size volume slider background + totalPosition = volumeTotal.position(), + + // the new top position based on the current volume + // 70% volume on 100px height == top:30px + newTop = totalHeight - (totalHeight * volume); + + // handle + volumeHandle.css('top', Math.round(totalPosition.top + newTop - (volumeHandle.height() / 2))); + + // show the current visibility + volumeCurrent.height(totalHeight - newTop ); + volumeCurrent.css('top', totalPosition.top + newTop); + } else { + var + + // height of the full size volume slider background + totalWidth = volumeTotal.width(), + + // top/left of full size volume slider background + totalPosition = volumeTotal.position(), + + // the new left position based on the current volume + newLeft = totalWidth * volume; + + // handle + volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2))); + + // rezize the current part of the volume bar + volumeCurrent.width( Math.round(newLeft) ); + } + }, + handleVolumeMove = function(e) { + + var volume = null, + totalOffset = volumeTotal.offset(); + + // calculate the new volume based on the moust position + if (mode == 'vertical') { + + var + railHeight = volumeTotal.height(), + totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), + newY = e.pageY - totalOffset.top; + + volume = (railHeight - newY) / railHeight; + + // the controls just hide themselves (usually when mouse moves too far up) + if (totalOffset.top == 0 || totalOffset.left == 0) + return; + + } else { + var + railWidth = volumeTotal.width(), + newX = e.pageX - totalOffset.left; + + volume = newX / railWidth; + } + + // ensure the volume isn't outside 0-1 + volume = Math.max(0,volume); + volume = Math.min(volume,1); + + // position the slider and handle + positionVolumeHandle(volume); + + // set the media object (this will trigger the volumechanged event) + if (volume == 0) { + media.setMuted(true); + } else { + media.setMuted(false); + } + media.setVolume(volume); + }, + mouseIsDown = false, + mouseIsOver = false; + + // SLIDER + + mute + .hover(function() { + volumeSlider.show(); + mouseIsOver = true; + }, function() { + mouseIsOver = false; + + if (!mouseIsDown && mode == 'vertical') { + volumeSlider.hide(); + } + }); + + volumeSlider + .bind('mouseover', function() { + mouseIsOver = true; + }) + .bind('mousedown', function (e) { + handleVolumeMove(e); + t.globalBind('mousemove.vol', function(e) { + handleVolumeMove(e); + }); + t.globalBind('mouseup.vol', function () { + mouseIsDown = false; + t.globalUnbind('.vol'); + + if (!mouseIsOver && mode == 'vertical') { + volumeSlider.hide(); + } + }); + mouseIsDown = true; + + return false; + }); + + + // MUTE button + mute.find('button').click(function() { + media.setMuted( !media.muted ); + }); + + // listen for volume change events from other sources + media.addEventListener('volumechange', function(e) { + if (!mouseIsDown) { + if (media.muted) { + positionVolumeHandle(0); + mute.removeClass('mejs-mute').addClass('mejs-unmute'); + } else { + positionVolumeHandle(media.volume); + mute.removeClass('mejs-unmute').addClass('mejs-mute'); + } + } + }, false); + + if (t.container.is(':visible')) { + // set initial volume + positionVolumeHandle(player.options.startVolume); + + // mutes the media and sets the volume icon muted if the initial volume is set to 0 + if (player.options.startVolume === 0) { + media.setMuted(true); + } + + // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements + if (media.pluginType === 'native') { + media.setVolume(player.options.startVolume); + } + } + } + }); + +})(mejs.$); + +(function($) { + + $.extend(mejs.MepDefaults, { + usePluginFullScreen: true, + newWindowCallback: function() { return '';}, + fullscreenText: mejs.i18n.t('Fullscreen') + }); + + $.extend(MediaElementPlayer.prototype, { + + isFullScreen: false, + + isNativeFullScreen: false, + + isInIframe: false, + + buildfullscreen: function(player, controls, layers, media) { + + if (!player.isVideo) + return; + + player.isInIframe = (window.location != window.parent.location); + + // native events + if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + + // chrome doesn't alays fire this in an iframe + var func = function(e) { + if (player.isFullScreen) { + if (mejs.MediaFeatures.isFullScreen()) { + player.isNativeFullScreen = true; + // reset the controls once we are fully in full screen + player.setControlsSize(); + } else { + player.isNativeFullScreen = false; + // when a user presses ESC + // make sure to put the player back into place + player.exitFullScreen(); + } + } + }; + + player.globalBind(mejs.MediaFeatures.fullScreenEventName, func); + } + + var t = this, + normalHeight = 0, + normalWidth = 0, + container = player.container, + fullscreenBtn = + $('<div class="mejs-button mejs-fullscreen-button">' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '" aria-label="' + t.options.fullscreenText + '"></button>' + + '</div>') + .appendTo(controls); + + if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) { + + fullscreenBtn.click(function() { + var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen; + + if (isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + }); + + } else { + + var hideTimeout = null, + supportsPointerEvents = (function() { + // TAKEN FROM MODERNIZR + var element = document.createElement('x'), + documentElement = document.documentElement, + getComputedStyle = window.getComputedStyle, + supports; + if(!('pointerEvents' in element.style)){ + return false; + } + element.style.pointerEvents = 'auto'; + element.style.pointerEvents = 'x'; + documentElement.appendChild(element); + supports = getComputedStyle && + getComputedStyle(element, '').pointerEvents === 'auto'; + documentElement.removeChild(element); + return !!supports; + })(); + + // + + if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :( + + // allows clicking through the fullscreen button and controls down directly to Flash + + /* + When a user puts his mouse over the fullscreen button, the controls are disabled + So we put a div over the video and another one on iether side of the fullscreen button + that caputre mouse movement + and restore the controls once the mouse moves outside of the fullscreen button + */ + + var fullscreenIsDisabled = false, + restoreControls = function() { + if (fullscreenIsDisabled) { + // hide the hovers + for (var i in hoverDivs) { + hoverDivs[i].hide(); + } + + // restore the control bar + fullscreenBtn.css('pointer-events', ''); + t.controls.css('pointer-events', ''); + + // prevent clicks from pausing video + t.media.removeEventListener('click', t.clickToPlayPauseCallback); + + // store for later + fullscreenIsDisabled = false; + } + }, + hoverDivs = {}, + hoverDivNames = ['top', 'left', 'right', 'bottom'], + i, len, + positionHoverDivs = function() { + var fullScreenBtnOffsetLeft = fullscreenBtn.offset().left - t.container.offset().left, + fullScreenBtnOffsetTop = fullscreenBtn.offset().top - t.container.offset().top, + fullScreenBtnWidth = fullscreenBtn.outerWidth(true), + fullScreenBtnHeight = fullscreenBtn.outerHeight(true), + containerWidth = t.container.width(), + containerHeight = t.container.height(); + + for (i in hoverDivs) { + hoverDivs[i].css({position: 'absolute', top: 0, left: 0}); //, backgroundColor: '#f00'}); + } + + // over video, but not controls + hoverDivs['top'] + .width( containerWidth ) + .height( fullScreenBtnOffsetTop ); + + // over controls, but not the fullscreen button + hoverDivs['left'] + .width( fullScreenBtnOffsetLeft ) + .height( fullScreenBtnHeight ) + .css({top: fullScreenBtnOffsetTop}); + + // after the fullscreen button + hoverDivs['right'] + .width( containerWidth - fullScreenBtnOffsetLeft - fullScreenBtnWidth ) + .height( fullScreenBtnHeight ) + .css({top: fullScreenBtnOffsetTop, + left: fullScreenBtnOffsetLeft + fullScreenBtnWidth}); + + // under the fullscreen button + hoverDivs['bottom'] + .width( containerWidth ) + .height( containerHeight - fullScreenBtnHeight - fullScreenBtnOffsetTop ) + .css({top: fullScreenBtnOffsetTop + fullScreenBtnHeight}); + }; + + t.globalBind('resize', function() { + positionHoverDivs(); + }); + + for (i = 0, len = hoverDivNames.length; i < len; i++) { + hoverDivs[hoverDivNames[i]] = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls).hide(); + } + + // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash + fullscreenBtn.on('mouseover',function() { + + if (!t.isFullScreen) { + + var buttonPos = fullscreenBtn.offset(), + containerPos = player.container.offset(); + + // move the button in Flash into place + media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false); + + // allows click through + fullscreenBtn.css('pointer-events', 'none'); + t.controls.css('pointer-events', 'none'); + + // restore click-to-play + t.media.addEventListener('click', t.clickToPlayPauseCallback); + + // show the divs that will restore things + for (i in hoverDivs) { + hoverDivs[i].show(); + } + + positionHoverDivs(); + + fullscreenIsDisabled = true; + } + + }); + + // restore controls anytime the user enters or leaves fullscreen + media.addEventListener('fullscreenchange', function(e) { + t.isFullScreen = !t.isFullScreen; + // don't allow plugin click to pause video - messes with + // plugin's controls + if (t.isFullScreen) { + t.media.removeEventListener('click', t.clickToPlayPauseCallback); + } else { + t.media.addEventListener('click', t.clickToPlayPauseCallback); + } + restoreControls(); + }); + + + // the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events + // so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button + + t.globalBind('mousemove', function(e) { + + // if the mouse is anywhere but the fullsceen button, then restore it all + if (fullscreenIsDisabled) { + + var fullscreenBtnPos = fullscreenBtn.offset(); + + + if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) || + e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true) + ) { + + fullscreenBtn.css('pointer-events', ''); + t.controls.css('pointer-events', ''); + + fullscreenIsDisabled = false; + } + } + }); + + + + } else { + + // the hover state will show the fullscreen button in Flash to hover up and click + + fullscreenBtn + .on('mouseover', function() { + + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + delete hideTimeout; + } + + var buttonPos = fullscreenBtn.offset(), + containerPos = player.container.offset(); + + media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true); + + }) + .on('mouseout', function() { + + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + delete hideTimeout; + } + + hideTimeout = setTimeout(function() { + media.hideFullscreenButton(); + }, 1500); + + + }); + } + } + + player.fullscreenBtn = fullscreenBtn; + + t.globalBind('keydown',function (e) { + if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) { + player.exitFullScreen(); + } + }); + + }, + + cleanfullscreen: function(player) { + player.exitFullScreen(); + }, + + containerSizeTimeout: null, + + enterFullScreen: function() { + + var t = this; + + // firefox+flash can't adjust plugin sizes without resetting :( + if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) { + //t.media.setFullscreen(true); + //player.isFullScreen = true; + return; + } + + // set it to not show scroll bars so 100% will work + $(document.documentElement).addClass('mejs-fullscreen'); + + // store sizing + normalHeight = t.container.height(); + normalWidth = t.container.width(); + + // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now) + if (t.media.pluginType === 'native') { + if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + + mejs.MediaFeatures.requestFullScreen(t.container[0]); + //return; + + if (t.isInIframe) { + // sometimes exiting from fullscreen doesn't work + // notably in Chrome <iframe>. Fixed in version 17 + setTimeout(function checkFullscreen() { + + if (t.isNativeFullScreen) { + var zoomMultiplier = window["devicePixelRatio"] || 1; + // Use a percent error margin since devicePixelRatio is a float and not exact. + var percentErrorMargin = 0.002; // 0.2% + var windowWidth = zoomMultiplier * $(window).width(); + var screenWidth = screen.width; + var absDiff = Math.abs(screenWidth - windowWidth); + var marginError = screenWidth * percentErrorMargin; + + // check if the video is suddenly not really fullscreen + if (absDiff > marginError) { + // manually exit + t.exitFullScreen(); + } else { + // test again + setTimeout(checkFullscreen, 500); + } + } + + + }, 500); + } + + } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) { + t.media.webkitEnterFullscreen(); + return; + } + } + + // check for iframe launch + if (t.isInIframe) { + var url = t.options.newWindowCallback(this); + + + if (url !== '') { + + // launch immediately + if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.pause(); + window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); + return; + } else { + setTimeout(function() { + if (!t.isNativeFullScreen) { + t.pause(); + window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); + } + }, 250); + } + } + + } + + // full window code + + + + // make full size + t.container + .addClass('mejs-container-fullscreen') + .width('100%') + .height('100%'); + //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); + + // Only needed for safari 5.1 native full screen, can cause display issues elsewhere + // Actually, it seems to be needed for IE8, too + //if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.containerSizeTimeout = setTimeout(function() { + t.container.css({width: '100%', height: '100%'}); + t.setControlsSize(); + }, 500); + //} + + if (t.media.pluginType === 'native') { + t.$media + .width('100%') + .height('100%'); + } else { + t.container.find('.mejs-shim') + .width('100%') + .height('100%'); + + //if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.media.setVideoSize($(window).width(),$(window).height()); + //} + } + + t.layers.children('div') + .width('100%') + .height('100%'); + + if (t.fullscreenBtn) { + t.fullscreenBtn + .removeClass('mejs-fullscreen') + .addClass('mejs-unfullscreen'); + } + + t.setControlsSize(); + t.isFullScreen = true; + + t.container.find('.mejs-captions-text').css('font-size', screen.width / t.width * 1.00 * 100 + '%'); + t.container.find('.mejs-captions-position').css('bottom', '45px'); + }, + + exitFullScreen: function() { + + var t = this; + + // Prevent container from attempting to stretch a second time + clearTimeout(t.containerSizeTimeout); + + // firefox can't adjust plugins + if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { + t.media.setFullscreen(false); + //player.isFullScreen = false; + return; + } + + // come outo of native fullscreen + if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) { + mejs.MediaFeatures.cancelFullScreen(); + } + + // restore scroll bars to document + $(document.documentElement).removeClass('mejs-fullscreen'); + + t.container + .removeClass('mejs-container-fullscreen') + .width(normalWidth) + .height(normalHeight); + //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); + + if (t.media.pluginType === 'native') { + t.$media + .width(normalWidth) + .height(normalHeight); + } else { + t.container.find('.mejs-shim') + .width(normalWidth) + .height(normalHeight); + + t.media.setVideoSize(normalWidth, normalHeight); + } + + t.layers.children('div') + .width(normalWidth) + .height(normalHeight); + + t.fullscreenBtn + .removeClass('mejs-unfullscreen') + .addClass('mejs-fullscreen'); + + t.setControlsSize(); + t.isFullScreen = false; + + t.container.find('.mejs-captions-text').css('font-size',''); + t.container.find('.mejs-captions-position').css('bottom', ''); + } + }); + +})(mejs.$); + +(function($) { + + // Speed + $.extend(mejs.MepDefaults, { + + speeds: ['1.50', '1.25', '1.00', '0.75'], + + defaultSpeed: '1.00' + + }); + + $.extend(MediaElementPlayer.prototype, { + + buildspeed: function(player, controls, layers, media) { + var t = this; + + if (t.media.pluginType == 'native') { + var s = '<div class="mejs-button mejs-speed-button"><button type="button">'+t.options.defaultSpeed+'x</button><div class="mejs-speed-selector"><ul>'; + var i, ss; + + if ($.inArray(t.options.defaultSpeed, t.options.speeds) === -1) { + t.options.speeds.push(t.options.defaultSpeed); + } + + t.options.speeds.sort(function(a, b) { + return parseFloat(b) - parseFloat(a); + }); + + for (i = 0; i < t.options.speeds.length; i++) { + s += '<li><input type="radio" name="speed" value="' + t.options.speeds[i] + '" id="' + t.options.speeds[i] + '" '; + if (t.options.speeds[i] == t.options.defaultSpeed) { + s += 'checked=true '; + s += '/><label for="' + t.options.speeds[i] + '" class="mejs-speed-selected">'+ t.options.speeds[i] + 'x</label></li>'; + } else { + s += '/><label for="' + t.options.speeds[i] + '">'+ t.options.speeds[i] + 'x</label></li>'; + } + } + s += '</ul></div></div>'; + + player.speedButton = $(s).appendTo(controls); + + player.playbackspeed = t.options.defaultSpeed; + + player.speedButton + .on('click', 'input[type=radio]', function() { + player.playbackspeed = $(this).attr('value'); + media.playbackRate = parseFloat(player.playbackspeed); + player.speedButton.find('button').text(player.playbackspeed + 'x'); + player.speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected'); + player.speedButton.find('input[type=radio]:checked').next().addClass('mejs-speed-selected'); + }); + + ss = player.speedButton.find('.mejs-speed-selector'); + ss.height(this.speedButton.find('.mejs-speed-selector ul').outerHeight(true) + player.speedButton.find('.mejs-speed-translations').outerHeight(true)); + ss.css('top', (-1 * ss.height()) + 'px'); + } + } + }); + +})(mejs.$); + +(function($) { + + // add extra default options + $.extend(mejs.MepDefaults, { + // this will automatically turn on a <track> + startLanguage: '', + + tracksText: mejs.i18n.t('Captions/Subtitles'), + + // option to remove the [cc] button when no <track kind="subtitles"> are present + hideCaptionsButtonWhenEmpty: true, + + // If true and we only have one track, change captions to popup + toggleCaptionsButtonWhenOnlyOne: false, + + // #id or .class + slidesSelector: '' + }); + + $.extend(MediaElementPlayer.prototype, { + + hasChapters: false, + + buildtracks: function(player, controls, layers, media) { + if (player.tracks.length === 0) + return; + + var t = this, + i, + options = ''; + + if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide + for (i = t.domNode.textTracks.length - 1; i >= 0; i--) { + t.domNode.textTracks[i].mode = "hidden"; + } + } + player.chapters = + $('<div class="mejs-chapters mejs-layer"></div>') + .prependTo(layers).hide(); + player.captions = + $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover"><span class="mejs-captions-text"></span></div></div>') + .prependTo(layers).hide(); + player.captionsText = player.captions.find('.mejs-captions-text'); + player.captionsButton = + $('<div class="mejs-button mejs-captions-button">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '" aria-label="' + t.options.tracksText + '"></button>'+ + '<div class="mejs-captions-selector">'+ + '<ul>'+ + '<li>'+ + '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' + + '<label for="' + player.id + '_captions_none">' + mejs.i18n.t('None') +'</label>'+ + '</li>' + + '</ul>'+ + '</div>'+ + '</div>') + .appendTo(controls); + + + var subtitleCount = 0; + for (i=0; i<player.tracks.length; i++) { + if (player.tracks[i].kind == 'subtitles') { + subtitleCount++; + } + } + + // if only one language then just make the button a toggle + if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitleCount == 1){ + // click + player.captionsButton.on('click',function() { + if (player.selectedTrack === null) { + lang = player.tracks[0].srclang; + } else { + lang = 'none'; + } + player.setTrack(lang); + }); + } else { + // hover or keyboard focus + player.captionsButton.on( 'mouseenter focusin', function() { + $(this).find('.mejs-captions-selector').css('visibility','visible'); + }) + + // handle clicks to the language radio buttons + .on('click','input[type=radio]',function() { + lang = this.value; + player.setTrack(lang); + }); + + player.captionsButton.on( 'mouseleave focusout', function() { + $(this).find(".mejs-captions-selector").css("visibility","hidden"); + }); + + } + + if (!player.options.alwaysShowControls) { + // move with controls + player.container + .bind('controlsshown', function () { + // push captions above controls + player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); + + }) + .bind('controlshidden', function () { + if (!media.paused) { + // move back to normal place + player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); + } + }); + } else { + player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); + } + + player.trackToLoad = -1; + player.selectedTrack = null; + player.isLoadingTrack = false; + + // add to list + for (i=0; i<player.tracks.length; i++) { + if (player.tracks[i].kind == 'subtitles') { + player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label); + } + } + + // start loading tracks + player.loadNextTrack(); + + media.addEventListener('timeupdate',function(e) { + player.displayCaptions(); + }, false); + + if (player.options.slidesSelector !== '') { + player.slidesContainer = $(player.options.slidesSelector); + + media.addEventListener('timeupdate',function(e) { + player.displaySlides(); + }, false); + + } + + media.addEventListener('loadedmetadata', function(e) { + player.displayChapters(); + }, false); + + player.container.hover( + function () { + // chapters + if (player.hasChapters) { + player.chapters.css('visibility','visible'); + player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight()); + } + }, + function () { + if (player.hasChapters && !media.paused) { + player.chapters.fadeOut(200, function() { + $(this).css('visibility','hidden'); + $(this).css('display','block'); + }); + } + }); + + // check for autoplay + if (player.node.getAttribute('autoplay') !== null) { + player.chapters.css('visibility','hidden'); + } + }, + + setTrack: function(lang){ + + var t = this, + i; + + if (lang == 'none') { + t.selectedTrack = null; + t.captionsButton.removeClass('mejs-captions-enabled'); + } else { + for (i=0; i<t.tracks.length; i++) { + if (t.tracks[i].srclang == lang) { + if (t.selectedTrack === null) + t.captionsButton.addClass('mejs-captions-enabled'); + t.selectedTrack = t.tracks[i]; + t.captions.attr('lang', t.selectedTrack.srclang); + t.displayCaptions(); + break; + } + } + } + }, + + loadNextTrack: function() { + var t = this; + + t.trackToLoad++; + if (t.trackToLoad < t.tracks.length) { + t.isLoadingTrack = true; + t.loadTrack(t.trackToLoad); + } else { + // add done? + t.isLoadingTrack = false; + + t.checkForTracks(); + } + }, + + loadTrack: function(index){ + var + t = this, + track = t.tracks[index], + after = function() { + + track.isLoaded = true; + + // create button + //t.addTrackButton(track.srclang); + t.enableTrackButton(track.srclang, track.label); + + t.loadNextTrack(); + + }; + + + $.ajax({ + url: track.src, + dataType: "text", + success: function(d) { + + // parse the loaded file + if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) { + track.entries = mejs.TrackFormatParser.dfxp.parse(d); + } else { + track.entries = mejs.TrackFormatParser.webvtt.parse(d); + } + + after(); + + if (track.kind == 'chapters') { + t.media.addEventListener('play', function(e) { + if (t.media.duration > 0) { + t.displayChapters(track); + } + }, false); + } + + if (track.kind == 'slides') { + t.setupSlides(track); + } + }, + error: function() { + t.loadNextTrack(); + } + }); + }, + + enableTrackButton: function(lang, label) { + var t = this; + + if (label === '') { + label = mejs.language.codes[lang] || lang; + } + + t.captionsButton + .find('input[value=' + lang + ']') + .prop('disabled',false) + .siblings('label') + .html( label ); + + // auto select + if (t.options.startLanguage == lang) { + $('#' + t.id + '_captions_' + lang).prop('checked', true).trigger('click'); + } + + t.adjustLanguageBox(); + }, + + addTrackButton: function(lang, label) { + var t = this; + if (label === '') { + label = mejs.language.codes[lang] || lang; + } + + t.captionsButton.find('ul').append( + $('<li>'+ + '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' + + '<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+ + '</li>') + ); + + t.adjustLanguageBox(); + + // remove this from the dropdownlist (if it exists) + t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove(); + }, + + adjustLanguageBox:function() { + var t = this; + // adjust the size of the outer box + t.captionsButton.find('.mejs-captions-selector').height( + t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) + + t.captionsButton.find('.mejs-captions-translations').outerHeight(true) + ); + }, + + checkForTracks: function() { + var + t = this, + hasSubtitles = false; + + // check if any subtitles + if (t.options.hideCaptionsButtonWhenEmpty) { + for (i=0; i<t.tracks.length; i++) { + if (t.tracks[i].kind == 'subtitles') { + hasSubtitles = true; + break; + } + } + + if (!hasSubtitles) { + t.captionsButton.hide(); + t.setControlsSize(); + } + } + }, + + displayCaptions: function() { + + if (typeof this.tracks == 'undefined') + return; + + var + t = this, + i, + track = t.selectedTrack; + + if (track !== null && track.isLoaded) { + for (i=0; i<track.entries.times.length; i++) { + if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop) { + // Set the line before the timecode as a class so the cue can be targeted if needed + t.captionsText.html(track.entries.text[i]).attr('class', 'mejs-captions-text ' + (track.entries.times[i].identifier || '')); + t.captions.show().height(0); + return; // exit out if one is visible; + } + } + t.captions.hide(); + } else { + t.captions.hide(); + } + }, + + setupSlides: function(track) { + var t = this; + + t.slides = track; + t.slides.entries.imgs = [t.slides.entries.text.length]; + t.showSlide(0); + + }, + + showSlide: function(index) { + if (typeof this.tracks == 'undefined' || typeof this.slidesContainer == 'undefined') { + return; + } + + var t = this, + url = t.slides.entries.text[index], + img = t.slides.entries.imgs[index]; + + if (typeof img == 'undefined' || typeof img.fadeIn == 'undefined') { + + t.slides.entries.imgs[index] = img = $('<img src="' + url + '">') + .on('load', function() { + img.appendTo(t.slidesContainer) + .hide() + .fadeIn() + .siblings(':visible') + .fadeOut(); + + }); + + } else { + + if (!img.is(':visible') && !img.is(':animated')) { + + // + + img.fadeIn() + .siblings(':visible') + .fadeOut(); + } + } + + }, + + displaySlides: function() { + + if (typeof this.slides == 'undefined') + return; + + var + t = this, + slides = t.slides, + i; + + for (i=0; i<slides.entries.times.length; i++) { + if (t.media.currentTime >= slides.entries.times[i].start && t.media.currentTime <= slides.entries.times[i].stop){ + + t.showSlide(i); + + return; // exit out if one is visible; + } + } + }, + + displayChapters: function() { + var + t = this, + i; + + for (i=0; i<t.tracks.length; i++) { + if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) { + t.drawChapters(t.tracks[i]); + t.hasChapters = true; + break; + } + } + }, + + drawChapters: function(chapters) { + var + t = this, + i, + dur, + //width, + //left, + percent = 0, + usedPercent = 0; + + t.chapters.empty(); + + for (i=0; i<chapters.entries.times.length; i++) { + dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; + percent = Math.floor(dur / t.media.duration * 100); + if (percent + usedPercent > 100 || // too large + i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in + { + percent = 100 - usedPercent; + } + //width = Math.floor(t.width * dur / t.media.duration); + //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); + //if (left + width > t.width) { + // width = t.width - left; + //} + + t.chapters.append( $( + '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + + '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + + '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + + '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + + '</div>' + + '</div>')); + usedPercent += percent; + } + + t.chapters.find('div.mejs-chapter').click(function() { + t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); + if (t.media.paused) { + t.media.play(); + } + }); + + t.chapters.show(); + } + }); + + + + mejs.language = { + codes: { + af:'Afrikaans', + sq:'Albanian', + ar:'Arabic', + be:'Belarusian', + bg:'Bulgarian', + ca:'Catalan', + zh:'Chinese', + 'zh-cn':'Chinese Simplified', + 'zh-tw':'Chinese Traditional', + hr:'Croatian', + cs:'Czech', + da:'Danish', + nl:'Dutch', + en:'English', + et:'Estonian', + fl:'Filipino', + fi:'Finnish', + fr:'French', + gl:'Galician', + de:'German', + el:'Greek', + ht:'Haitian Creole', + iw:'Hebrew', + hi:'Hindi', + hu:'Hungarian', + is:'Icelandic', + id:'Indonesian', + ga:'Irish', + it:'Italian', + ja:'Japanese', + ko:'Korean', + lv:'Latvian', + lt:'Lithuanian', + mk:'Macedonian', + ms:'Malay', + mt:'Maltese', + no:'Norwegian', + fa:'Persian', + pl:'Polish', + pt:'Portuguese', + // 'pt-pt':'Portuguese (Portugal)', + ro:'Romanian', + ru:'Russian', + sr:'Serbian', + sk:'Slovak', + sl:'Slovenian', + es:'Spanish', + sw:'Swahili', + sv:'Swedish', + tl:'Tagalog', + th:'Thai', + tr:'Turkish', + uk:'Ukrainian', + vi:'Vietnamese', + cy:'Welsh', + yi:'Yiddish' + } + }; + + /* + Parses WebVTT format which should be formatted as + ================================ + WEBVTT + + 1 + 00:00:01,1 --> 00:00:05,000 + A line of text + + 2 + 00:01:15,1 --> 00:02:05,000 + A second line of text + + =============================== + + Adapted from: http://www.delphiki.com/html5/playr + */ + mejs.TrackFormatParser = { + webvtt: { + pattern_timecode: /^((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, + + parse: function(trackText) { + var + i = 0, + lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/), + entries = {text:[], times:[]}, + timecode, + text, + identifier; + for(; i<lines.length; i++) { + timecode = this.pattern_timecode.exec(lines[i]); + + if (timecode && i<lines.length) { + if ((i - 1) >= 0 && lines[i - 1] !== '') { + identifier = lines[i - 1]; + } + i++; + // grab all the (possibly multi-line) text that follows + text = lines[i]; + i++; + while(lines[i] !== '' && i<lines.length){ + text = text + '\n' + lines[i]; + i++; + } + text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); + // Text is in a different array so I can use .join + entries.text.push(text); + entries.times.push( + { + identifier: identifier, + start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) === 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]), + stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]), + settings: timecode[5] + }); + } + identifier = ''; + } + return entries; + } + }, + // Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420 + dfxp: { + parse: function(trackText) { + trackText = $(trackText).filter("tt"); + var + i = 0, + container = trackText.children("div").eq(0), + lines = container.find("p"), + styleNode = trackText.find("#" + container.attr("style")), + styles, + begin, + end, + text, + entries = {text:[], times:[]}; + + + if (styleNode.length) { + var attributes = styleNode.removeAttr("id").get(0).attributes; + if (attributes.length) { + styles = {}; + for (i = 0; i < attributes.length; i++) { + styles[attributes[i].name.split(":")[1]] = attributes[i].value; + } + } + } + + for(i = 0; i<lines.length; i++) { + var style; + var _temp_times = { + start: null, + stop: null, + style: null + }; + if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin")); + if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end")); + if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end")); + if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin")); + if (styles) { + style = ""; + for (var _style in styles) { + style += _style + ":" + styles[_style] + ";"; + } + } + if (style) _temp_times.style = style; + if (_temp_times.start === 0) _temp_times.start = 0.200; + entries.times.push(_temp_times); + text = $.trim(lines.eq(i).html()).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); + entries.text.push(text); + if (entries.times.start === 0) entries.times.start = 2; + } + return entries; + } + }, + split2: function (text, regex) { + // normal version for compliant browsers + // see below for IE fix + return text.split(regex); + } + }; + + // test for browsers with bad String.split method. + if ('x\n\ny'.split(/\n/gi).length != 3) { + // add super slow IE8 and below version + mejs.TrackFormatParser.split2 = function(text, regex) { + var + parts = [], + chunk = '', + i; + + for (i=0; i<text.length; i++) { + chunk += text.substring(i,i+1); + if (regex.test(chunk)) { + parts.push(chunk.replace(regex, '')); + chunk = ''; + } + } + parts.push(chunk); + return parts; + }; + } + +})(mejs.$); + +/* +* ContextMenu Plugin +* +* +*/ + +(function($) { + +$.extend(mejs.MepDefaults, + { 'contextMenuItems': [ + // demo of a fullscreen option + { + render: function(player) { + + // check for fullscreen plugin + if (typeof player.enterFullScreen == 'undefined') + return null; + + if (player.isFullScreen) { + return mejs.i18n.t('Turn off Fullscreen'); + } else { + return mejs.i18n.t('Go Fullscreen'); + } + }, + click: function(player) { + if (player.isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + } + } + , + // demo of a mute/unmute button + { + render: function(player) { + if (player.media.muted) { + return mejs.i18n.t('Unmute'); + } else { + return mejs.i18n.t('Mute'); + } + }, + click: function(player) { + if (player.media.muted) { + player.setMuted(false); + } else { + player.setMuted(true); + } + } + }, + // separator + { + isSeparator: true + } + , + // demo of simple download video + { + render: function(player) { + return mejs.i18n.t('Download Video'); + }, + click: function(player) { + window.location.href = player.media.currentSrc; + } + } + ]} +); + + + $.extend(MediaElementPlayer.prototype, { + buildcontextmenu: function(player, controls, layers, media) { + + // create context menu + player.contextMenu = $('<div class="mejs-contextmenu"></div>') + .appendTo($('body')) + .hide(); + + // create events for showing context menu + player.container.bind('contextmenu', function(e) { + if (player.isContextMenuEnabled) { + e.preventDefault(); + player.renderContextMenu(e.clientX-1, e.clientY-1); + return false; + } + }); + player.container.bind('click', function() { + player.contextMenu.hide(); + }); + player.contextMenu.bind('mouseleave', function() { + + // + player.startContextMenuTimer(); + + }); + }, + + cleancontextmenu: function(player) { + player.contextMenu.remove(); + }, + + isContextMenuEnabled: true, + enableContextMenu: function() { + this.isContextMenuEnabled = true; + }, + disableContextMenu: function() { + this.isContextMenuEnabled = false; + }, + + contextMenuTimeout: null, + startContextMenuTimer: function() { + // + + var t = this; + + t.killContextMenuTimer(); + + t.contextMenuTimer = setTimeout(function() { + t.hideContextMenu(); + t.killContextMenuTimer(); + }, 750); + }, + killContextMenuTimer: function() { + var timer = this.contextMenuTimer; + + // + + if (timer != null) { + clearTimeout(timer); + delete timer; + timer = null; + } + }, + + hideContextMenu: function() { + this.contextMenu.hide(); + }, + + renderContextMenu: function(x,y) { + + // alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly + var t = this, + html = '', + items = t.options.contextMenuItems; + + for (var i=0, il=items.length; i<il; i++) { + + if (items[i].isSeparator) { + html += '<div class="mejs-contextmenu-separator"></div>'; + } else { + + var rendered = items[i].render(t); + + // render can return null if the item doesn't need to be used at the moment + if (rendered != null) { + html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>'; + } + } + } + + // position and show the context menu + t.contextMenu + .empty() + .append($(html)) + .css({top:y, left:x}) + .show(); + + // bind events + t.contextMenu.find('.mejs-contextmenu-item').each(function() { + + // which one is this? + var $dom = $(this), + itemIndex = parseInt( $dom.data('itemindex'), 10 ), + item = t.options.contextMenuItems[itemIndex]; + + // bind extra functionality? + if (typeof item.show != 'undefined') + item.show( $dom , t); + + // bind click action + $dom.click(function() { + // perform click action + if (typeof item.click != 'undefined') + item.click(t); + + // close + t.contextMenu.hide(); + }); + }); + + // stop the controls from hiding + setTimeout(function() { + t.killControlsTimer('rev3'); + }, 100); + + } + }); + +})(mejs.$); +/** + * Postroll plugin + */ +(function($) { + + $.extend(mejs.MepDefaults, { + postrollCloseText: mejs.i18n.t('Close') + }); + + // Postroll + $.extend(MediaElementPlayer.prototype, { + buildpostroll: function(player, controls, layers, media) { + var + t = this, + postrollLink = t.container.find('link[rel="postroll"]').attr('href'); + + if (typeof postrollLink !== 'undefined') { + player.postroll = + $('<div class="mejs-postroll-layer mejs-layer"><a class="mejs-postroll-close" onclick="$(this).parent().hide();return false;">' + t.options.postrollCloseText + '</a><div class="mejs-postroll-layer-content"></div></div>').prependTo(layers).hide(); + + t.media.addEventListener('ended', function (e) { + $.ajax({ + dataType: 'html', + url: postrollLink, + success: function (data, textStatus) { + layers.find('.mejs-postroll-layer-content').html(data); + } + }); + player.postroll.show(); + }, false); + } + } + }); + +})(mejs.$); + diff --git a/js/mediaelement/build/mediaelement-and-player.min.js b/js/mediaelement/build/mediaelement-and-player.min.js new file mode 100644 index 0000000000000000000000000000000000000000..f8a16c4c0c5e63276de9523dddfa77dbf3f71dd8 --- /dev/null +++ b/js/mediaelement/build/mediaelement-and-player.min.js @@ -0,0 +1,183 @@ +/*! +* MediaElement.js +* HTML5 <video> and <audio> shim and player +* http://mediaelementjs.com/ +* +* Creates a JavaScript object that mimics HTML5 MediaElement API +* for browsers that don't understand HTML5 or can't play the provided codec +* Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3 +* +* Copyright 2010-2014, John Dyer (http://j.hn) +* License: MIT +* +*/var mejs=mejs||{};mejs.version="2.15.1";mejs.meIndex=0; +mejs.plugins={silverlight:[{version:[3,0],types:["video/mp4","video/m4v","video/mov","video/wmv","audio/wma","audio/m4a","audio/mp3","audio/wav","audio/mpeg"]}],flash:[{version:[9,0,124],types:["video/mp4","video/m4v","video/mov","video/flv","video/rtmp","video/x-flv","audio/flv","audio/x-flv","audio/mp3","audio/m4a","audio/mpeg","video/youtube","video/x-youtube","application/x-mpegURL"]}],youtube:[{version:null,types:["video/youtube","video/x-youtube","audio/youtube","audio/x-youtube"]}],vimeo:[{version:null, +types:["video/vimeo","video/x-vimeo"]}]}; +mejs.Utility={encodeUrl:function(a){return encodeURIComponent(a)},escapeHTML:function(a){return a.toString().split("&").join("&").split("<").join("<").split('"').join(""")},absolutizeUrl:function(a){var b=document.createElement("div");b.innerHTML='<a href="'+this.escapeHTML(a)+'">x</a>';return b.firstChild.href},getScriptPath:function(a){for(var b=0,c,d="",e="",g,f,i=document.getElementsByTagName("script"),k=i.length,h=a.length;b<k;b++){g=i[b].src;c=g.lastIndexOf("/");if(c>-1){f=g.substring(c+ +1);g=g.substring(0,c+1)}else{f=g;g=""}for(c=0;c<h;c++){e=a[c];e=f.indexOf(e);if(e>-1){d=g;break}}if(d!=="")break}return d},secondsToTimeCode:function(a,b,c,d){if(typeof c=="undefined")c=false;else if(typeof d=="undefined")d=25;var e=Math.floor(a/3600)%24,g=Math.floor(a/60)%60,f=Math.floor(a%60);a=Math.floor((a%1*d).toFixed(3));return(b||e>0?(e<10?"0"+e:e)+":":"")+(g<10?"0"+g:g)+":"+(f<10?"0"+f:f)+(c?":"+(a<10?"0"+a:a):"")},timeCodeToSeconds:function(a,b,c,d){if(typeof c=="undefined")c=false;else if(typeof d== +"undefined")d=25;a=a.split(":");b=parseInt(a[0],10);var e=parseInt(a[1],10),g=parseInt(a[2],10),f=0,i=0;if(c)f=parseInt(a[3])/d;return i=b*3600+e*60+g+f},convertSMPTEtoSeconds:function(a){if(typeof a!="string")return false;a=a.replace(",",".");var b=0,c=a.indexOf(".")!=-1?a.split(".")[1].length:0,d=1;a=a.split(":").reverse();for(var e=0;e<a.length;e++){d=1;if(e>0)d=Math.pow(60,e);b+=Number(a[e])*d}return Number(b.toFixed(c))},removeSwf:function(a){var b=document.getElementById(a);if(b&&/object|embed/i.test(b.nodeName))if(mejs.MediaFeatures.isIE){b.style.display= +"none";(function(){b.readyState==4?mejs.Utility.removeObjectInIE(a):setTimeout(arguments.callee,10)})()}else b.parentNode.removeChild(b)},removeObjectInIE:function(a){if(a=document.getElementById(a)){for(var b in a)if(typeof a[b]=="function")a[b]=null;a.parentNode.removeChild(a)}}}; +mejs.PluginDetector={hasPluginVersion:function(a,b){var c=this.plugins[a];b[1]=b[1]||0;b[2]=b[2]||0;return c[0]>b[0]||c[0]==b[0]&&c[1]>b[1]||c[0]==b[0]&&c[1]==b[1]&&c[2]>=b[2]?true:false},nav:window.navigator,ua:window.navigator.userAgent.toLowerCase(),plugins:[],addPlugin:function(a,b,c,d,e){this.plugins[a]=this.detectPlugin(b,c,d,e)},detectPlugin:function(a,b,c,d){var e=[0,0,0],g;if(typeof this.nav.plugins!="undefined"&&typeof this.nav.plugins[a]=="object"){if((c=this.nav.plugins[a].description)&& +!(typeof this.nav.mimeTypes!="undefined"&&this.nav.mimeTypes[b]&&!this.nav.mimeTypes[b].enabledPlugin)){e=c.replace(a,"").replace(/^\s+/,"").replace(/\sr/gi,".").split(".");for(a=0;a<e.length;a++)e[a]=parseInt(e[a].match(/\d+/),10)}}else if(typeof window.ActiveXObject!="undefined")try{if(g=new ActiveXObject(c))e=d(g)}catch(f){}return e}}; +mejs.PluginDetector.addPlugin("flash","Shockwave Flash","application/x-shockwave-flash","ShockwaveFlash.ShockwaveFlash",function(a){var b=[];if(a=a.GetVariable("$version")){a=a.split(" ")[1].split(",");b=[parseInt(a[0],10),parseInt(a[1],10),parseInt(a[2],10)]}return b}); +mejs.PluginDetector.addPlugin("silverlight","Silverlight Plug-In","application/x-silverlight-2","AgControl.AgControl",function(a){var b=[0,0,0,0],c=function(d,e,g,f){for(;d.isVersionSupported(e[0]+"."+e[1]+"."+e[2]+"."+e[3]);)e[g]+=f;e[g]-=f};c(a,b,0,1);c(a,b,1,1);c(a,b,2,1E4);c(a,b,2,1E3);c(a,b,2,100);c(a,b,2,10);c(a,b,2,1);c(a,b,3,1);return b}); +mejs.MediaFeatures={init:function(){var a=this,b=document,c=mejs.PluginDetector.nav,d=mejs.PluginDetector.ua.toLowerCase(),e,g=["source","track","audio","video"];a.isiPad=d.match(/ipad/i)!==null;a.isiPhone=d.match(/iphone/i)!==null;a.isiOS=a.isiPhone||a.isiPad;a.isAndroid=d.match(/android/i)!==null;a.isBustedAndroid=d.match(/android 2\.[12]/)!==null;a.isBustedNativeHTTPS=location.protocol==="https:"&&(d.match(/android [12]\./)!==null||d.match(/macintosh.* version.* safari/)!==null);a.isIE=c.appName.toLowerCase().indexOf("microsoft")!= +-1||c.appName.toLowerCase().match(/trident/gi)!==null;a.isChrome=d.match(/chrome/gi)!==null;a.isChromium=d.match(/chromium/gi)!==null;a.isFirefox=d.match(/firefox/gi)!==null;a.isWebkit=d.match(/webkit/gi)!==null;a.isGecko=d.match(/gecko/gi)!==null&&!a.isWebkit&&!a.isIE;a.isOpera=d.match(/opera/gi)!==null;a.hasTouch="ontouchstart"in window;a.svg=!!document.createElementNS&&!!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect;for(c=0;c<g.length;c++)e=document.createElement(g[c]); +a.supportsMediaTag=typeof e.canPlayType!=="undefined"||a.isBustedAndroid;try{e.canPlayType("video/mp4")}catch(f){a.supportsMediaTag=false}a.hasSemiNativeFullScreen=typeof e.webkitEnterFullscreen!=="undefined";a.hasNativeFullscreen=typeof e.requestFullscreen!=="undefined";a.hasWebkitNativeFullScreen=typeof e.webkitRequestFullScreen!=="undefined";a.hasMozNativeFullScreen=typeof e.mozRequestFullScreen!=="undefined";a.hasMsNativeFullScreen=typeof e.msRequestFullscreen!=="undefined";a.hasTrueNativeFullScreen= +a.hasWebkitNativeFullScreen||a.hasMozNativeFullScreen||a.hasMsNativeFullScreen;a.nativeFullScreenEnabled=a.hasTrueNativeFullScreen;if(a.hasMozNativeFullScreen)a.nativeFullScreenEnabled=document.mozFullScreenEnabled;else if(a.hasMsNativeFullScreen)a.nativeFullScreenEnabled=document.msFullscreenEnabled;if(a.isChrome)a.hasSemiNativeFullScreen=false;if(a.hasTrueNativeFullScreen){a.fullScreenEventName="";if(a.hasWebkitNativeFullScreen)a.fullScreenEventName="webkitfullscreenchange";else if(a.hasMozNativeFullScreen)a.fullScreenEventName= +"mozfullscreenchange";else if(a.hasMsNativeFullScreen)a.fullScreenEventName="MSFullscreenChange";a.isFullScreen=function(){if(a.hasMozNativeFullScreen)return b.mozFullScreen;else if(a.hasWebkitNativeFullScreen)return b.webkitIsFullScreen;else if(a.hasMsNativeFullScreen)return b.msFullscreenElement!==null};a.requestFullScreen=function(i){if(a.hasWebkitNativeFullScreen)i.webkitRequestFullScreen();else if(a.hasMozNativeFullScreen)i.mozRequestFullScreen();else a.hasMsNativeFullScreen&&i.msRequestFullscreen()}; +a.cancelFullScreen=function(){if(a.hasWebkitNativeFullScreen)document.webkitCancelFullScreen();else if(a.hasMozNativeFullScreen)document.mozCancelFullScreen();else a.hasMsNativeFullScreen&&document.msExitFullscreen()}}if(a.hasSemiNativeFullScreen&&d.match(/mac os x 10_5/i)){a.hasNativeFullScreen=false;a.hasSemiNativeFullScreen=false}}};mejs.MediaFeatures.init(); +mejs.HtmlMediaElement={pluginType:"native",isFullScreen:false,setCurrentTime:function(a){this.currentTime=a},setMuted:function(a){this.muted=a},setVolume:function(a){this.volume=a},stop:function(){this.pause()},setSrc:function(a){for(var b=this.getElementsByTagName("source");b.length>0;)this.removeChild(b[0]);if(typeof a=="string")this.src=a;else{var c;for(b=0;b<a.length;b++){c=a[b];if(this.canPlayType(c.type)){this.src=c.src;break}}}},setVideoSize:function(a,b){this.width=a;this.height=b}}; +mejs.PluginMediaElement=function(a,b,c){this.id=a;this.pluginType=b;this.src=c;this.events={};this.attributes={}}; +mejs.PluginMediaElement.prototype={pluginElement:null,pluginType:"",isFullScreen:false,playbackRate:-1,defaultPlaybackRate:-1,seekable:[],played:[],paused:true,ended:false,seeking:false,duration:0,error:null,tagName:"",muted:false,volume:1,currentTime:0,play:function(){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType=="vimeo"?this.pluginApi.playVideo():this.pluginApi.playMedia();this.paused=false}},load:function(){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType== +"vimeo"||this.pluginApi.loadMedia();this.paused=false}},pause:function(){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType=="vimeo"?this.pluginApi.pauseVideo():this.pluginApi.pauseMedia();this.paused=true}},stop:function(){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType=="vimeo"?this.pluginApi.stopVideo():this.pluginApi.stopMedia();this.paused=true}},canPlayType:function(a){var b,c,d,e=mejs.plugins[this.pluginType];for(b=0;b<e.length;b++){d=e[b];if(mejs.PluginDetector.hasPluginVersion(this.pluginType, +d.version))for(c=0;c<d.types.length;c++)if(a==d.types[c])return"probably"}return""},positionFullscreenButton:function(a,b,c){this.pluginApi!=null&&this.pluginApi.positionFullscreenButton&&this.pluginApi.positionFullscreenButton(Math.floor(a),Math.floor(b),c)},hideFullscreenButton:function(){this.pluginApi!=null&&this.pluginApi.hideFullscreenButton&&this.pluginApi.hideFullscreenButton()},setSrc:function(a){if(typeof a=="string"){this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(a));this.src=mejs.Utility.absolutizeUrl(a)}else{var b, +c;for(b=0;b<a.length;b++){c=a[b];if(this.canPlayType(c.type)){this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(c.src));this.src=mejs.Utility.absolutizeUrl(a);break}}}},setCurrentTime:function(a){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType=="vimeo"?this.pluginApi.seekTo(a):this.pluginApi.setCurrentTime(a);this.currentTime=a}},setVolume:function(a){if(this.pluginApi!=null){this.pluginType=="youtube"?this.pluginApi.setVolume(a*100):this.pluginApi.setVolume(a);this.volume=a}}, +setMuted:function(a){if(this.pluginApi!=null){if(this.pluginType=="youtube"){a?this.pluginApi.mute():this.pluginApi.unMute();this.muted=a;this.dispatchEvent("volumechange")}else this.pluginApi.setMuted(a);this.muted=a}},setVideoSize:function(a,b){if(this.pluginElement&&this.pluginElement.style){this.pluginElement.style.width=a+"px";this.pluginElement.style.height=b+"px"}this.pluginApi!=null&&this.pluginApi.setVideoSize&&this.pluginApi.setVideoSize(a,b)},setFullscreen:function(a){this.pluginApi!=null&& +this.pluginApi.setFullscreen&&this.pluginApi.setFullscreen(a)},enterFullScreen:function(){this.pluginApi!=null&&this.pluginApi.setFullscreen&&this.setFullscreen(true)},exitFullScreen:function(){this.pluginApi!=null&&this.pluginApi.setFullscreen&&this.setFullscreen(false)},addEventListener:function(a,b){this.events[a]=this.events[a]||[];this.events[a].push(b)},removeEventListener:function(a,b){if(!a){this.events={};return true}var c=this.events[a];if(!c)return true;if(!b){this.events[a]=[];return true}for(var d= +0;d<c.length;d++)if(c[d]===b){this.events[a].splice(d,1);return true}return false},dispatchEvent:function(a){var b,c,d=this.events[a];if(d){c=Array.prototype.slice.call(arguments,1);for(b=0;b<d.length;b++)d[b].apply(null,c)}},hasAttribute:function(a){return a in this.attributes},removeAttribute:function(a){delete this.attributes[a]},getAttribute:function(a){if(this.hasAttribute(a))return this.attributes[a];return""},setAttribute:function(a,b){this.attributes[a]=b},remove:function(){mejs.Utility.removeSwf(this.pluginElement.id); +mejs.MediaPluginBridge.unregisterPluginElement(this.pluginElement.id)}}; +mejs.MediaPluginBridge={pluginMediaElements:{},htmlMediaElements:{},registerPluginElement:function(a,b,c){this.pluginMediaElements[a]=b;this.htmlMediaElements[a]=c},unregisterPluginElement:function(a){delete this.pluginMediaElements[a];delete this.htmlMediaElements[a]},initPlugin:function(a){var b=this.pluginMediaElements[a],c=this.htmlMediaElements[a];if(b){switch(b.pluginType){case "flash":b.pluginElement=b.pluginApi=document.getElementById(a);break;case "silverlight":b.pluginElement=document.getElementById(b.id); +b.pluginApi=b.pluginElement.Content.MediaElementJS}b.pluginApi!=null&&b.success&&b.success(b,c)}},fireEvent:function(a,b,c){var d,e;if(a=this.pluginMediaElements[a]){b={type:b,target:a};for(d in c){a[d]=c[d];b[d]=c[d]}e=c.bufferedTime||0;b.target.buffered=b.buffered={start:function(){return 0},end:function(){return e},length:1};a.dispatchEvent(b.type,b)}}}; +mejs.MediaElementDefaults={mode:"auto",plugins:["flash","silverlight","youtube","vimeo"],enablePluginDebug:false,httpsBasicAuthSite:false,type:"",pluginPath:mejs.Utility.getScriptPath(["mediaelement.js","mediaelement.min.js","mediaelement-and-player.js","mediaelement-and-player.min.js"]),flashName:"flashmediaelement.swf",flashStreamer:"",enablePluginSmoothing:false,enablePseudoStreaming:false,pseudoStreamingStartQueryParam:"start",silverlightName:"silverlightmediaelement.xap",defaultVideoWidth:480, +defaultVideoHeight:270,pluginWidth:-1,pluginHeight:-1,pluginVars:[],timerRate:250,startVolume:0.8,success:function(){},error:function(){}};mejs.MediaElement=function(a,b){return mejs.HtmlMediaElementShim.create(a,b)}; +mejs.HtmlMediaElementShim={create:function(a,b){var c=mejs.MediaElementDefaults,d=typeof a=="string"?document.getElementById(a):a,e=d.tagName.toLowerCase(),g=e==="audio"||e==="video",f=g?d.getAttribute("src"):d.getAttribute("href");e=d.getAttribute("poster");var i=d.getAttribute("autoplay"),k=d.getAttribute("preload"),h=d.getAttribute("controls"),j;for(j in b)c[j]=b[j];f=typeof f=="undefined"||f===null||f==""?null:f;e=typeof e=="undefined"||e===null?"":e;k=typeof k=="undefined"||k===null||k==="false"? +"none":k;i=!(typeof i=="undefined"||i===null||i==="false");h=!(typeof h=="undefined"||h===null||h==="false");j=this.determinePlayback(d,c,mejs.MediaFeatures.supportsMediaTag,g,f);j.url=j.url!==null?mejs.Utility.absolutizeUrl(j.url):"";if(j.method=="native"){if(mejs.MediaFeatures.isBustedAndroid){d.src=j.url;d.addEventListener("click",function(){d.play()},false)}return this.updateNative(j,c,i,k)}else if(j.method!=="")return this.createPlugin(j,c,e,i,k,h);else{this.createErrorMessage(j,c,e);return this}}, +determinePlayback:function(a,b,c,d,e){var g=[],f,i,k,h={method:"",url:"",htmlMediaElement:a,isVideo:a.tagName.toLowerCase()!="audio"},j;if(typeof b.type!="undefined"&&b.type!=="")if(typeof b.type=="string")g.push({type:b.type,url:e});else for(f=0;f<b.type.length;f++)g.push({type:b.type[f],url:e});else if(e!==null){k=this.formatType(e,a.getAttribute("type"));g.push({type:k,url:e})}else for(f=0;f<a.childNodes.length;f++){i=a.childNodes[f];if(i.nodeType==1&&i.tagName.toLowerCase()=="source"){e=i.getAttribute("src"); +k=this.formatType(e,i.getAttribute("type"));i=i.getAttribute("media");if(!i||!window.matchMedia||window.matchMedia&&window.matchMedia(i).matches)g.push({type:k,url:e})}}if(!d&&g.length>0&&g[0].url!==null&&this.getTypeFromFile(g[0].url).indexOf("audio")>-1)h.isVideo=false;if(mejs.MediaFeatures.isBustedAndroid)a.canPlayType=function(m){return m.match(/video\/(mp4|m4v)/gi)!==null?"maybe":""};if(mejs.MediaFeatures.isChromium)a.canPlayType=function(m){return m.match(/video\/(webm|ogv|ogg)/gi)!==null?"maybe": +""};if(c&&(b.mode==="auto"||b.mode==="auto_plugin"||b.mode==="native")&&!(mejs.MediaFeatures.isBustedNativeHTTPS&&b.httpsBasicAuthSite===true)){if(!d){f=document.createElement(h.isVideo?"video":"audio");a.parentNode.insertBefore(f,a);a.style.display="none";h.htmlMediaElement=a=f}for(f=0;f<g.length;f++)if(g[f].type=="video/m3u8"||a.canPlayType(g[f].type).replace(/no/,"")!==""||a.canPlayType(g[f].type.replace(/mp3/,"mpeg")).replace(/no/,"")!==""||a.canPlayType(g[f].type.replace(/m4a/,"mp4")).replace(/no/, +"")!==""){h.method="native";h.url=g[f].url;break}if(h.method==="native"){if(h.url!==null)a.src=h.url;if(b.mode!=="auto_plugin")return h}}if(b.mode==="auto"||b.mode==="auto_plugin"||b.mode==="shim")for(f=0;f<g.length;f++){k=g[f].type;for(a=0;a<b.plugins.length;a++){e=b.plugins[a];i=mejs.plugins[e];for(c=0;c<i.length;c++){j=i[c];if(j.version==null||mejs.PluginDetector.hasPluginVersion(e,j.version))for(d=0;d<j.types.length;d++)if(k==j.types[d]){h.method=e;h.url=g[f].url;return h}}}}if(b.mode==="auto_plugin"&& +h.method==="native")return h;if(h.method===""&&g.length>0)h.url=g[0].url;return h},formatType:function(a,b){return a&&!b?this.getTypeFromFile(a):b&&~b.indexOf(";")?b.substr(0,b.indexOf(";")):b},getTypeFromFile:function(a){a=a.split("?")[0];a=a.substring(a.lastIndexOf(".")+1).toLowerCase();return(/(mp4|m4v|ogg|ogv|m3u8|webm|webmv|flv|wmv|mpeg|mov)/gi.test(a)?"video":"audio")+"/"+this.getTypeFromExtension(a)},getTypeFromExtension:function(a){switch(a){case "mp4":case "m4v":case "m4a":return"mp4";case "webm":case "webma":case "webmv":return"webm"; +case "ogg":case "oga":case "ogv":return"ogg";default:return a}},createErrorMessage:function(a,b,c){var d=a.htmlMediaElement,e=document.createElement("div");e.className="me-cannotplay";try{e.style.width=d.width+"px";e.style.height=d.height+"px"}catch(g){}e.innerHTML=b.customError?b.customError:c!==""?'<a href="'+a.url+'"><img src="'+c+'" width="100%" height="100%" /></a>':'<a href="'+a.url+'"><span>'+mejs.i18n.t("Download File")+"</span></a>";d.parentNode.insertBefore(e,d);d.style.display="none";b.error(d)}, +createPlugin:function(a,b,c,d,e,g){c=a.htmlMediaElement;var f=1,i=1,k="me_"+a.method+"_"+mejs.meIndex++,h=new mejs.PluginMediaElement(k,a.method,a.url),j=document.createElement("div"),m;h.tagName=c.tagName;for(m=0;m<c.attributes.length;m++){var q=c.attributes[m];q.specified==true&&h.setAttribute(q.name,q.value)}for(m=c.parentNode;m!==null&&m.tagName.toLowerCase()!=="body"&&m.parentNode!=null;){if(m.parentNode.tagName.toLowerCase()==="p"){m.parentNode.parentNode.insertBefore(m,m.parentNode);break}m= +m.parentNode}if(a.isVideo){f=b.pluginWidth>0?b.pluginWidth:b.videoWidth>0?b.videoWidth:c.getAttribute("width")!==null?c.getAttribute("width"):b.defaultVideoWidth;i=b.pluginHeight>0?b.pluginHeight:b.videoHeight>0?b.videoHeight:c.getAttribute("height")!==null?c.getAttribute("height"):b.defaultVideoHeight;f=mejs.Utility.encodeUrl(f);i=mejs.Utility.encodeUrl(i)}else if(b.enablePluginDebug){f=320;i=240}h.success=b.success;mejs.MediaPluginBridge.registerPluginElement(k,h,c);j.className="me-plugin";j.id= +k+"_container";a.isVideo?c.parentNode.insertBefore(j,c):document.body.insertBefore(j,document.body.childNodes[0]);d=["id="+k,"isvideo="+(a.isVideo?"true":"false"),"autoplay="+(d?"true":"false"),"preload="+e,"width="+f,"startvolume="+b.startVolume,"timerrate="+b.timerRate,"flashstreamer="+b.flashStreamer,"height="+i,"pseudostreamstart="+b.pseudoStreamingStartQueryParam];if(a.url!==null)a.method=="flash"?d.push("file="+mejs.Utility.encodeUrl(a.url)):d.push("file="+a.url);b.enablePluginDebug&&d.push("debug=true"); +b.enablePluginSmoothing&&d.push("smoothing=true");b.enablePseudoStreaming&&d.push("pseudostreaming=true");g&&d.push("controls=true");if(b.pluginVars)d=d.concat(b.pluginVars);switch(a.method){case "silverlight":j.innerHTML='<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="'+k+'" name="'+k+'" width="'+f+'" height="'+i+'" class="mejs-shim"><param name="initParams" value="'+d.join(",")+'" /><param name="windowless" value="true" /><param name="background" value="black" /><param name="minRuntimeVersion" value="3.0.0.0" /><param name="autoUpgrade" value="true" /><param name="source" value="'+ +b.pluginPath+b.silverlightName+'" /></object>';break;case "flash":if(mejs.MediaFeatures.isIE){a=document.createElement("div");j.appendChild(a);a.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" id="'+k+'" width="'+f+'" height="'+i+'" class="mejs-shim"><param name="movie" value="'+b.pluginPath+b.flashName+"?x="+new Date+'" /><param name="flashvars" value="'+d.join("&")+'" /><param name="quality" value="high" /><param name="bgcolor" value="#000000" /><param name="wmode" value="transparent" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /><param name="scale" value="default" /></object>'}else j.innerHTML= +'<embed id="'+k+'" name="'+k+'" play="true" loop="false" quality="high" bgcolor="#000000" wmode="transparent" allowScriptAccess="always" allowFullScreen="true" type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" src="'+b.pluginPath+b.flashName+'" flashvars="'+d.join("&")+'" width="'+f+'" height="'+i+'" scale="default"class="mejs-shim"></embed>';break;case "youtube":if(a.url.lastIndexOf("youtu.be")!=-1){a=a.url.substr(a.url.lastIndexOf("/")+1);if(a.indexOf("?")!= +-1)a=a.substr(0,a.indexOf("?"))}else a=a.url.substr(a.url.lastIndexOf("=")+1);youtubeSettings={container:j,containerId:j.id,pluginMediaElement:h,pluginId:k,videoId:a,height:i,width:f};mejs.PluginDetector.hasPluginVersion("flash",[10,0,0])?mejs.YouTubeApi.createFlash(youtubeSettings):mejs.YouTubeApi.enqueueIframe(youtubeSettings);break;case "vimeo":b=k+"_player";h.vimeoid=a.url.substr(a.url.lastIndexOf("/")+1);j.innerHTML='<iframe src="//player.vimeo.com/video/'+h.vimeoid+"?api=1&portrait=0&byline=0&title=0&player_id="+ +b+'" width="'+f+'" height="'+i+'" frameborder="0" class="mejs-shim" id="'+b+'"></iframe>';if(typeof $f=="function"){var l=$f(j.childNodes[0]);l.addEvent("ready",function(){function o(n,p,r,s){n={type:r,target:p};if(r=="timeupdate"){p.currentTime=n.currentTime=s.seconds;p.duration=n.duration=s.duration}p.dispatchEvent(n.type,n)}$.extend(l,{playVideo:function(){l.api("play")},stopVideo:function(){l.api("unload")},pauseVideo:function(){l.api("pause")},seekTo:function(n){l.api("seekTo",n)},setVolume:function(n){l.api("setVolume", +n)},setMuted:function(n){if(n){l.lastVolume=l.api("getVolume");l.api("setVolume",0)}else{l.api("setVolume",l.lastVolume);delete l.lastVolume}}});l.addEvent("play",function(){o(l,h,"play");o(l,h,"playing")});l.addEvent("pause",function(){o(l,h,"pause")});l.addEvent("finish",function(){o(l,h,"ended")});l.addEvent("playProgress",function(n){o(l,h,"timeupdate",n)});h.pluginElement=j;h.pluginApi=l;mejs.MediaPluginBridge.initPlugin(k)})}else console.warn("You need to include froogaloop for vimeo to work")}c.style.display= +"none";c.removeAttribute("autoplay");return h},updateNative:function(a,b){var c=a.htmlMediaElement,d;for(d in mejs.HtmlMediaElement)c[d]=mejs.HtmlMediaElement[d];b.success(c,c);return c}}; +mejs.YouTubeApi={isIframeStarted:false,isIframeLoaded:false,loadIframeApi:function(){if(!this.isIframeStarted){var a=document.createElement("script");a.src="//www.youtube.com/player_api";var b=document.getElementsByTagName("script")[0];b.parentNode.insertBefore(a,b);this.isIframeStarted=true}},iframeQueue:[],enqueueIframe:function(a){if(this.isLoaded)this.createIframe(a);else{this.loadIframeApi();this.iframeQueue.push(a)}},createIframe:function(a){var b=a.pluginMediaElement,c=new YT.Player(a.containerId, +{height:a.height,width:a.width,videoId:a.videoId,playerVars:{controls:0},events:{onReady:function(){a.pluginMediaElement.pluginApi=c;mejs.MediaPluginBridge.initPlugin(a.pluginId);setInterval(function(){mejs.YouTubeApi.createEvent(c,b,"timeupdate")},250)},onStateChange:function(d){mejs.YouTubeApi.handleStateChange(d.data,c,b)}}})},createEvent:function(a,b,c){c={type:c,target:b};if(a&&a.getDuration){b.currentTime=c.currentTime=a.getCurrentTime();b.duration=c.duration=a.getDuration();c.paused=b.paused; +c.ended=b.ended;c.muted=a.isMuted();c.volume=a.getVolume()/100;c.bytesTotal=a.getVideoBytesTotal();c.bufferedBytes=a.getVideoBytesLoaded();var d=c.bufferedBytes/c.bytesTotal*c.duration;c.target.buffered=c.buffered={start:function(){return 0},end:function(){return d},length:1}}b.dispatchEvent(c.type,c)},iFrameReady:function(){for(this.isIframeLoaded=this.isLoaded=true;this.iframeQueue.length>0;)this.createIframe(this.iframeQueue.pop())},flashPlayers:{},createFlash:function(a){this.flashPlayers[a.pluginId]= +a;var b,c="//www.youtube.com/apiplayer?enablejsapi=1&playerapiid="+a.pluginId+"&version=3&autoplay=0&controls=0&modestbranding=1&loop=0";if(mejs.MediaFeatures.isIE){b=document.createElement("div");a.container.appendChild(b);b.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" id="'+a.pluginId+'" width="'+a.width+'" height="'+a.height+'" class="mejs-shim"><param name="movie" value="'+ +c+'" /><param name="wmode" value="transparent" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /></object>'}else a.container.innerHTML='<object type="application/x-shockwave-flash" id="'+a.pluginId+'" data="'+c+'" width="'+a.width+'" height="'+a.height+'" style="visibility: visible; " class="mejs-shim"><param name="allowScriptAccess" value="always"><param name="wmode" value="transparent"></object>'},flashReady:function(a){var b=this.flashPlayers[a],c= +document.getElementById(a),d=b.pluginMediaElement;d.pluginApi=d.pluginElement=c;mejs.MediaPluginBridge.initPlugin(a);c.cueVideoById(b.videoId);a=b.containerId+"_callback";window[a]=function(e){mejs.YouTubeApi.handleStateChange(e,c,d)};c.addEventListener("onStateChange",a);setInterval(function(){mejs.YouTubeApi.createEvent(c,d,"timeupdate")},250);mejs.YouTubeApi.createEvent(c,d,"canplay")},handleStateChange:function(a,b,c){switch(a){case -1:c.paused=true;c.ended=true;mejs.YouTubeApi.createEvent(b, +c,"loadedmetadata");break;case 0:c.paused=false;c.ended=true;mejs.YouTubeApi.createEvent(b,c,"ended");break;case 1:c.paused=false;c.ended=false;mejs.YouTubeApi.createEvent(b,c,"play");mejs.YouTubeApi.createEvent(b,c,"playing");break;case 2:c.paused=true;c.ended=false;mejs.YouTubeApi.createEvent(b,c,"pause");break;case 3:mejs.YouTubeApi.createEvent(b,c,"progress")}}};function onYouTubePlayerAPIReady(){mejs.YouTubeApi.iFrameReady()}function onYouTubePlayerReady(a){mejs.YouTubeApi.flashReady(a)} +window.mejs=mejs;window.MediaElement=mejs.MediaElement; +(function(a,b){var c={locale:{language:"",strings:{}},methods:{}};c.getLanguage=function(){return(c.locale.language||window.navigator.userLanguage||window.navigator.language).substr(0,2).toLowerCase()};if(typeof mejsL10n!="undefined")c.locale.language=mejsL10n.language;c.methods.checkPlain=function(d){var e,g,f={"&":"&",'"':""","<":"<",">":">"};d=String(d);for(e in f)if(f.hasOwnProperty(e)){g=RegExp(e,"g");d=d.replace(g,f[e])}return d};c.methods.t=function(d,e){if(c.locale.strings&& +c.locale.strings[e.context]&&c.locale.strings[e.context][d])d=c.locale.strings[e.context][d];return c.methods.checkPlain(d)};c.t=function(d,e){if(typeof d==="string"&&d.length>0){var g=c.getLanguage();e=e||{context:g};return c.methods.t(d,e)}else throw{name:"InvalidArgumentException",message:"First argument is either not a string or empty."};};b.i18n=c})(document,mejs);(function(a){if(typeof mejsL10n!="undefined")a[mejsL10n.language]=mejsL10n.strings})(mejs.i18n.locale.strings); +(function(a){if(typeof a.de==="undefined")a.de={Fullscreen:"Vollbild","Go Fullscreen":"Vollbild an","Turn off Fullscreen":"Vollbild aus",Close:"Schlie\u00dfen"}})(mejs.i18n.locale.strings);(function(a){if(typeof a.zh==="undefined")a.zh={Fullscreen:"\u5168\u87a2\u5e55","Go Fullscreen":"\u5168\u5c4f\u6a21\u5f0f","Turn off Fullscreen":"\u9000\u51fa\u5168\u5c4f\u6a21\u5f0f",Close:"\u95dc\u9589"}})(mejs.i18n.locale.strings); + +/*! + * MediaElementPlayer + * http://mediaelementjs.com/ + * + * Creates a controller bar for HTML5 <video> add <audio> tags + * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) + * + * Copyright 2010-2013, John Dyer (http://j.hn/) + * License: MIT + * + */if(typeof jQuery!="undefined")mejs.$=jQuery;else if(typeof ender!="undefined")mejs.$=ender; +(function(f){mejs.MepDefaults={poster:"",showPosterWhenEnded:false,defaultVideoWidth:480,defaultVideoHeight:270,videoWidth:-1,videoHeight:-1,defaultAudioWidth:400,defaultAudioHeight:30,defaultSeekBackwardInterval:function(a){return a.duration*0.05},defaultSeekForwardInterval:function(a){return a.duration*0.05},setDimensions:true,audioWidth:-1,audioHeight:-1,startVolume:0.8,loop:false,autoRewind:true,enableAutosize:true,alwaysShowHours:false,showTimecodeFrameCount:false,framesPerSecond:25,autosizeProgress:true, +alwaysShowControls:false,hideVideoControlsOnLoad:false,clickToPlayPause:true,iPadUseNativeControls:false,iPhoneUseNativeControls:false,AndroidUseNativeControls:false,features:["playpause","current","progress","duration","tracks","volume","fullscreen"],isVideo:true,enableKeyboard:true,pauseOtherPlayers:true,keyActions:[{keys:[32,179],action:function(a,b){b.paused||b.ended?a.play():a.pause()}},{keys:[38],action:function(a,b){a.container.find(".mejs-volume-slider").css("display","block");if(a.isVideo){a.showControls(); +a.startControlsTimer()}b.setVolume(Math.min(b.volume+0.1,1))}},{keys:[40],action:function(a,b){a.container.find(".mejs-volume-slider").css("display","block");if(a.isVideo){a.showControls();a.startControlsTimer()}b.setVolume(Math.max(b.volume-0.1,0))}},{keys:[37,227],action:function(a,b){if(!isNaN(b.duration)&&b.duration>0){if(a.isVideo){a.showControls();a.startControlsTimer()}var c=Math.max(b.currentTime-a.options.defaultSeekBackwardInterval(b),0);b.setCurrentTime(c)}}},{keys:[39,228],action:function(a, +b){if(!isNaN(b.duration)&&b.duration>0){if(a.isVideo){a.showControls();a.startControlsTimer()}var c=Math.min(b.currentTime+a.options.defaultSeekForwardInterval(b),b.duration);b.setCurrentTime(c)}}},{keys:[70],action:function(a){if(typeof a.enterFullScreen!="undefined")a.isFullScreen?a.exitFullScreen():a.enterFullScreen()}},{keys:[77],action:function(a){a.container.find(".mejs-volume-slider").css("display","block");if(a.isVideo){a.showControls();a.startControlsTimer()}a.media.muted?a.setMuted(false): +a.setMuted(true)}}]};mejs.mepIndex=0;mejs.players={};mejs.MediaElementPlayer=function(a,b){if(!(this instanceof mejs.MediaElementPlayer))return new mejs.MediaElementPlayer(a,b);this.$media=this.$node=f(a);this.node=this.media=this.$media[0];if(typeof this.node.player!="undefined")return this.node.player;else this.node.player=this;if(typeof b=="undefined")b=this.$node.data("mejsoptions");this.options=f.extend({},mejs.MepDefaults,b);this.id="mep_"+mejs.mepIndex++;mejs.players[this.id]=this;this.init(); +return this};mejs.MediaElementPlayer.prototype={hasFocus:false,controlsAreVisible:true,init:function(){var a=this,b=mejs.MediaFeatures,c=f.extend(true,{},a.options,{success:function(d,g){a.meReady(d,g)},error:function(d){a.handleError(d)}}),e=a.media.tagName.toLowerCase();a.isDynamic=e!=="audio"&&e!=="video";a.isVideo=a.isDynamic?a.options.isVideo:e!=="audio"&&a.options.isVideo;if(b.isiPad&&a.options.iPadUseNativeControls||b.isiPhone&&a.options.iPhoneUseNativeControls){a.$media.attr("controls","controls"); +b.isiPad&&a.media.getAttribute("autoplay")!==null&&a.play()}else if(!(b.isAndroid&&a.options.AndroidUseNativeControls)){a.$media.removeAttr("controls");a.container=f('<div id="'+a.id+'" class="mejs-container '+(mejs.MediaFeatures.svg?"svg":"no-svg")+'"><div class="mejs-inner"><div class="mejs-mediaelement"></div><div class="mejs-layers"></div><div class="mejs-controls"></div><div class="mejs-clear"></div></div></div>').addClass(a.$media[0].className).insertBefore(a.$media);a.container.addClass((b.isAndroid? +"mejs-android ":"")+(b.isiOS?"mejs-ios ":"")+(b.isiPad?"mejs-ipad ":"")+(b.isiPhone?"mejs-iphone ":"")+(a.isVideo?"mejs-video ":"mejs-audio "));if(b.isiOS){b=a.$media.clone();a.container.find(".mejs-mediaelement").append(b);a.$media.remove();a.$node=a.$media=b;a.node=a.media=b[0]}else a.container.find(".mejs-mediaelement").append(a.$media);a.controls=a.container.find(".mejs-controls");a.layers=a.container.find(".mejs-layers");b=a.isVideo?"video":"audio";e=b.substring(0,1).toUpperCase()+b.substring(1); +a.width=a.options[b+"Width"]>0||a.options[b+"Width"].toString().indexOf("%")>-1?a.options[b+"Width"]:a.media.style.width!==""&&a.media.style.width!==null?a.media.style.width:a.media.getAttribute("width")!==null?a.$media.attr("width"):a.options["default"+e+"Width"];a.height=a.options[b+"Height"]>0||a.options[b+"Height"].toString().indexOf("%")>-1?a.options[b+"Height"]:a.media.style.height!==""&&a.media.style.height!==null?a.media.style.height:a.$media[0].getAttribute("height")!==null?a.$media.attr("height"): +a.options["default"+e+"Height"];a.setPlayerSize(a.width,a.height);c.pluginWidth=a.width;c.pluginHeight=a.height}mejs.MediaElement(a.$media[0],c);typeof a.container!="undefined"&&a.controlsAreVisible&&a.container.trigger("controlsshown")},showControls:function(a){var b=this;a=typeof a=="undefined"||a;if(!b.controlsAreVisible){if(a){b.controls.css("visibility","visible").stop(true,true).fadeIn(200,function(){b.controlsAreVisible=true;b.container.trigger("controlsshown")});b.container.find(".mejs-control").css("visibility", +"visible").stop(true,true).fadeIn(200,function(){b.controlsAreVisible=true})}else{b.controls.css("visibility","visible").css("display","block");b.container.find(".mejs-control").css("visibility","visible").css("display","block");b.controlsAreVisible=true;b.container.trigger("controlsshown")}b.setControlsSize()}},hideControls:function(a){var b=this;a=typeof a=="undefined"||a;if(!(!b.controlsAreVisible||b.options.alwaysShowControls))if(a){b.controls.stop(true,true).fadeOut(200,function(){f(this).css("visibility", +"hidden").css("display","block");b.controlsAreVisible=false;b.container.trigger("controlshidden")});b.container.find(".mejs-control").stop(true,true).fadeOut(200,function(){f(this).css("visibility","hidden").css("display","block")})}else{b.controls.css("visibility","hidden").css("display","block");b.container.find(".mejs-control").css("visibility","hidden").css("display","block");b.controlsAreVisible=false;b.container.trigger("controlshidden")}},controlsTimer:null,startControlsTimer:function(a){var b= +this;a=typeof a!="undefined"?a:1500;b.killControlsTimer("start");b.controlsTimer=setTimeout(function(){b.hideControls();b.killControlsTimer("hide")},a)},killControlsTimer:function(){if(this.controlsTimer!==null){clearTimeout(this.controlsTimer);delete this.controlsTimer;this.controlsTimer=null}},controlsEnabled:true,disableControls:function(){this.killControlsTimer();this.hideControls(false);this.controlsEnabled=false},enableControls:function(){this.showControls(false);this.controlsEnabled=true}, +meReady:function(a,b){var c=this,e=mejs.MediaFeatures,d=b.getAttribute("autoplay");d=!(typeof d=="undefined"||d===null||d==="false");var g;if(!c.created){c.created=true;c.media=a;c.domNode=b;if(!(e.isAndroid&&c.options.AndroidUseNativeControls)&&!(e.isiPad&&c.options.iPadUseNativeControls)&&!(e.isiPhone&&c.options.iPhoneUseNativeControls)){c.buildposter(c,c.controls,c.layers,c.media);c.buildkeyboard(c,c.controls,c.layers,c.media);c.buildoverlays(c,c.controls,c.layers,c.media);c.findTracks();for(g in c.options.features){e= +c.options.features[g];if(c["build"+e])try{c["build"+e](c,c.controls,c.layers,c.media)}catch(k){}}c.container.trigger("controlsready");c.setPlayerSize(c.width,c.height);c.setControlsSize();if(c.isVideo){if(mejs.MediaFeatures.hasTouch)c.$media.bind("touchstart",function(){if(c.controlsAreVisible)c.hideControls(false);else c.controlsEnabled&&c.showControls(false)});else{c.clickToPlayPauseCallback=function(){if(c.options.clickToPlayPause)c.media.paused?c.play():c.pause()};c.media.addEventListener("click", +c.clickToPlayPauseCallback,false);c.container.bind("mouseenter mouseover",function(){if(c.controlsEnabled)if(!c.options.alwaysShowControls){c.killControlsTimer("enter");c.showControls();c.startControlsTimer(2500)}}).bind("mousemove",function(){if(c.controlsEnabled){c.controlsAreVisible||c.showControls();c.options.alwaysShowControls||c.startControlsTimer(2500)}}).bind("mouseleave",function(){c.controlsEnabled&&!c.media.paused&&!c.options.alwaysShowControls&&c.startControlsTimer(1E3)})}c.options.hideVideoControlsOnLoad&& +c.hideControls(false);d&&!c.options.alwaysShowControls&&c.hideControls();c.options.enableAutosize&&c.media.addEventListener("loadedmetadata",function(j){if(c.options.videoHeight<=0&&c.domNode.getAttribute("height")===null&&!isNaN(j.target.videoHeight)){c.setPlayerSize(j.target.videoWidth,j.target.videoHeight);c.setControlsSize();c.media.setVideoSize(j.target.videoWidth,j.target.videoHeight)}},false)}a.addEventListener("play",function(){for(var j in mejs.players){var m=mejs.players[j];m.id!=c.id&& +c.options.pauseOtherPlayers&&!m.paused&&!m.ended&&m.pause();m.hasFocus=false}c.hasFocus=true},false);c.media.addEventListener("ended",function(){if(c.options.autoRewind)try{c.media.setCurrentTime(0)}catch(j){}c.media.pause();c.setProgressRail&&c.setProgressRail();c.setCurrentRail&&c.setCurrentRail();if(c.options.loop)c.play();else!c.options.alwaysShowControls&&c.controlsEnabled&&c.showControls()},false);c.media.addEventListener("loadedmetadata",function(){c.updateDuration&&c.updateDuration();c.updateCurrent&& +c.updateCurrent();if(!c.isFullScreen){c.setPlayerSize(c.width,c.height);c.setControlsSize()}},false);setTimeout(function(){c.setPlayerSize(c.width,c.height);c.setControlsSize()},50);c.globalBind("resize",function(){c.isFullScreen||mejs.MediaFeatures.hasTrueNativeFullScreen&&document.webkitIsFullScreen||c.setPlayerSize(c.width,c.height);c.setControlsSize()});c.media.pluginType=="youtube"&&c.options.autoplay&&c.container.find(".mejs-overlay-play").hide()}d&&a.pluginType=="native"&&c.play();if(c.options.success)typeof c.options.success== +"string"?window[c.options.success](c.media,c.domNode,c):c.options.success(c.media,c.domNode,c)}},handleError:function(a){this.controls.hide();this.options.error&&this.options.error(a)},setPlayerSize:function(a,b){if(!this.options.setDimensions)return false;if(typeof a!="undefined")this.width=a;if(typeof b!="undefined")this.height=b;if(this.height.toString().indexOf("%")>0||this.$node.css("max-width")==="100%"||this.$node[0].currentStyle&&this.$node[0].currentStyle.maxWidth==="100%"){var c=this.isVideo? +this.media.videoWidth&&this.media.videoWidth>0?this.media.videoWidth:this.media.getAttribute("width")!==null?this.media.getAttribute("width"):this.options.defaultVideoWidth:this.options.defaultAudioWidth,e=this.isVideo?this.media.videoHeight&&this.media.videoHeight>0?this.media.videoHeight:this.media.getAttribute("height")!==null?this.media.getAttribute("height"):this.options.defaultVideoHeight:this.options.defaultAudioHeight,d=this.container.parent().closest(":visible").width(),g=this.container.parent().closest(":visible").height(); +c=this.isVideo||!this.options.autosizeProgress?parseInt(d*e/c,10):e;if(isNaN(c)||g!=0&&c>g)c=g;if(this.container.parent()[0].tagName.toLowerCase()==="body"){d=f(window).width();c=f(window).height()}if(c!=0&&d!=0){this.container.width(d).height(c);this.$media.add(this.container.find(".mejs-shim")).width("100%").height("100%");this.isVideo&&this.media.setVideoSize&&this.media.setVideoSize(d,c);this.layers.children(".mejs-layer").width("100%").height("100%")}}else{this.container.width(this.width).height(this.height); +this.layers.children(".mejs-layer").width(this.width).height(this.height)}d=this.layers.find(".mejs-overlay-play");g=d.find(".mejs-overlay-button");d.height(this.container.height()-this.controls.height());g.css("margin-top","-"+(g.height()/2-this.controls.height()/2).toString()+"px")},setControlsSize:function(){var a=0,b=0,c=this.controls.find(".mejs-time-rail"),e=this.controls.find(".mejs-time-total");this.controls.find(".mejs-time-current");this.controls.find(".mejs-time-loaded");var d=c.siblings(), +g=d.last(),k=null;if(!(!this.container.is(":visible")||!c.length||!c.is(":visible"))){if(this.options&&!this.options.autosizeProgress)b=parseInt(c.css("width"));if(b===0||!b){d.each(function(){var j=f(this);if(j.css("position")!="absolute"&&j.is(":visible"))a+=f(this).outerWidth(true)});b=this.controls.width()-a-(c.outerWidth(true)-c.width())}do{c.width(b);e.width(b-(e.outerWidth(true)-e.width()));if(g.css("position")!="absolute"){k=g.position();b--}}while(k!=null&&k.top>0&&b>0);this.setProgressRail&& +this.setProgressRail();this.setCurrentRail&&this.setCurrentRail()}},buildposter:function(a,b,c,e){var d=f('<div class="mejs-poster mejs-layer"></div>').appendTo(c);b=a.$media.attr("poster");if(a.options.poster!=="")b=a.options.poster;b!==""&&b!=null?this.setPoster(b):d.hide();e.addEventListener("play",function(){d.hide()},false);a.options.showPosterWhenEnded&&a.options.autoRewind&&e.addEventListener("ended",function(){d.show()},false)},setPoster:function(a){var b=this.container.find(".mejs-poster"), +c=b.find("img");if(c.length==0)c=f('<img width="100%" height="100%" />').appendTo(b);c.attr("src",a);b.css({"background-image":"url("+a+")"})},buildoverlays:function(a,b,c,e){var d=this;if(a.isVideo){var g=f('<div class="mejs-overlay mejs-layer"><div class="mejs-overlay-loading"><span></span></div></div>').hide().appendTo(c),k=f('<div class="mejs-overlay mejs-layer"><div class="mejs-overlay-error"></div></div>').hide().appendTo(c),j=f('<div class="mejs-overlay mejs-layer mejs-overlay-play"><div class="mejs-overlay-button"></div></div>').appendTo(c).bind("click", +function(){d.options.clickToPlayPause&&e.paused&&e.play()});e.addEventListener("play",function(){j.hide();g.hide();b.find(".mejs-time-buffering").hide();k.hide()},false);e.addEventListener("playing",function(){j.hide();g.hide();b.find(".mejs-time-buffering").hide();k.hide()},false);e.addEventListener("seeking",function(){g.show();b.find(".mejs-time-buffering").show()},false);e.addEventListener("seeked",function(){g.hide();b.find(".mejs-time-buffering").hide()},false);e.addEventListener("pause",function(){mejs.MediaFeatures.isiPhone|| +j.show()},false);e.addEventListener("waiting",function(){g.show();b.find(".mejs-time-buffering").show()},false);e.addEventListener("loadeddata",function(){g.show();b.find(".mejs-time-buffering").show()},false);e.addEventListener("canplay",function(){g.hide();b.find(".mejs-time-buffering").hide()},false);e.addEventListener("error",function(){g.hide();b.find(".mejs-time-buffering").hide();k.show();k.find("mejs-overlay-error").html("Error loading this resource")},false);e.addEventListener("keydown", +function(m){d.onkeydown(a,e,m)},false)}},buildkeyboard:function(a,b,c,e){var d=this;d.globalBind("keydown",function(g){return d.onkeydown(a,e,g)});d.globalBind("click",function(g){a.hasFocus=f(g.target).closest(".mejs-container").length!=0})},onkeydown:function(a,b,c){if(a.hasFocus&&a.options.enableKeyboard)for(var e=0,d=a.options.keyActions.length;e<d;e++)for(var g=a.options.keyActions[e],k=0,j=g.keys.length;k<j;k++)if(c.keyCode==g.keys[k]){typeof c.preventDefault=="function"&&c.preventDefault(); +g.action(a,b,c.keyCode);return false}return true},findTracks:function(){var a=this,b=a.$media.find("track");a.tracks=[];b.each(function(c,e){e=f(e);a.tracks.push({srclang:e.attr("srclang")?e.attr("srclang").toLowerCase():"",src:e.attr("src"),kind:e.attr("kind"),label:e.attr("label")||"",entries:[],isLoaded:false})})},changeSkin:function(a){this.container[0].className="mejs-container "+a;this.setPlayerSize(this.width,this.height);this.setControlsSize()},play:function(){this.load();this.media.play()}, +pause:function(){try{this.media.pause()}catch(a){}},load:function(){this.isLoaded||this.media.load();this.isLoaded=true},setMuted:function(a){this.media.setMuted(a)},setCurrentTime:function(a){this.media.setCurrentTime(a)},getCurrentTime:function(){return this.media.currentTime},setVolume:function(a){this.media.setVolume(a)},getVolume:function(){return this.media.volume},setSrc:function(a){this.media.setSrc(a)},remove:function(){var a,b;for(a in this.options.features){b=this.options.features[a];if(this["clean"+ +b])try{this["clean"+b](this)}catch(c){}}if(this.isDynamic)this.$node.insertBefore(this.container);else{this.$media.prop("controls",true);this.$node.clone().insertBefore(this.container).show();this.$node.remove()}this.media.pluginType!=="native"&&this.media.remove();delete mejs.players[this.id];typeof this.container=="object"&&this.container.remove();this.globalUnbind();delete this.node.player}};(function(){function a(c,e){var d={d:[],w:[]};f.each((c||"").split(" "),function(g,k){var j=k+"."+e;if(j.indexOf(".")=== +0){d.d.push(j);d.w.push(j)}else d[b.test(k)?"w":"d"].push(j)});d.d=d.d.join(" ");d.w=d.w.join(" ");return d}var b=/^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/;mejs.MediaElementPlayer.prototype.globalBind=function(c,e,d){c=a(c,this.id);c.d&&f(document).bind(c.d,e,d);c.w&&f(window).bind(c.w,e,d)};mejs.MediaElementPlayer.prototype.globalUnbind=function(c,e){c=a(c,this.id);c.d&&f(document).unbind(c.d,e);c.w&&f(window).unbind(c.w,e)}})(); +if(typeof f!="undefined"){f.fn.mediaelementplayer=function(a){a===false?this.each(function(){var b=f(this).data("mediaelementplayer");b&&b.remove();f(this).removeData("mediaelementplayer")}):this.each(function(){f(this).data("mediaelementplayer",new mejs.MediaElementPlayer(this,a))});return this};f(document).ready(function(){f(".mejs-player").mediaelementplayer()})}window.MediaElementPlayer=mejs.MediaElementPlayer})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{playpauseText:mejs.i18n.t("Play/Pause")});f.extend(MediaElementPlayer.prototype,{buildplaypause:function(a,b,c,e){var d=f('<div class="mejs-button mejs-playpause-button mejs-play" ><button type="button" aria-controls="'+this.id+'" title="'+this.options.playpauseText+'" aria-label="'+this.options.playpauseText+'"></button></div>').appendTo(b).click(function(g){g.preventDefault();e.paused?e.play():e.pause();return false});e.addEventListener("play",function(){d.removeClass("mejs-play").addClass("mejs-pause")}, +false);e.addEventListener("playing",function(){d.removeClass("mejs-play").addClass("mejs-pause")},false);e.addEventListener("pause",function(){d.removeClass("mejs-pause").addClass("mejs-play")},false);e.addEventListener("paused",function(){d.removeClass("mejs-pause").addClass("mejs-play")},false)}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{stopText:"Stop"});f.extend(MediaElementPlayer.prototype,{buildstop:function(a,b,c,e){f('<div class="mejs-button mejs-stop-button mejs-stop"><button type="button" aria-controls="'+this.id+'" title="'+this.options.stopText+'" aria-label="'+this.options.stopText+'"></button></div>').appendTo(b).click(function(){e.paused||e.pause();if(e.currentTime>0){e.setCurrentTime(0);e.pause();b.find(".mejs-time-current").width("0px");b.find(".mejs-time-handle").css("left", +"0px");b.find(".mejs-time-float-current").html(mejs.Utility.secondsToTimeCode(0));b.find(".mejs-currenttime").html(mejs.Utility.secondsToTimeCode(0));c.find(".mejs-poster").show()}})}})})(mejs.$); +(function(f){f.extend(MediaElementPlayer.prototype,{buildprogress:function(a,b,c,e){f('<div class="mejs-time-rail"><span class="mejs-time-total"><span class="mejs-time-buffering"></span><span class="mejs-time-loaded"></span><span class="mejs-time-current"></span><span class="mejs-time-handle"></span><span class="mejs-time-float"><span class="mejs-time-float-current">00:00</span><span class="mejs-time-float-corner"></span></span></span></div>').appendTo(b);b.find(".mejs-time-buffering").hide();var d= +this,g=b.find(".mejs-time-total");c=b.find(".mejs-time-loaded");var k=b.find(".mejs-time-current"),j=b.find(".mejs-time-handle"),m=b.find(".mejs-time-float"),q=b.find(".mejs-time-float-current"),p=function(h){h=h.originalEvent.changedTouches?h.originalEvent.changedTouches[0].pageX:h.pageX;var l=g.offset(),r=g.outerWidth(true),n=0,o=n=0;if(e.duration){if(h<l.left)h=l.left;else if(h>r+l.left)h=r+l.left;o=h-l.left;n=o/r;n=n<=0.02?0:n*e.duration;t&&n!==e.currentTime&&e.setCurrentTime(n);if(!mejs.MediaFeatures.hasTouch){m.css("left", +o);q.html(mejs.Utility.secondsToTimeCode(n));m.show()}}},t=false;g.bind("mousedown touchstart",function(h){if(h.which===1||h.which===0){t=true;p(h);d.globalBind("mousemove.dur touchmove.dur",function(l){p(l)});d.globalBind("mouseup.dur touchend.dur",function(){t=false;m.hide();d.globalUnbind(".dur")});return false}}).bind("mouseenter",function(){d.globalBind("mousemove.dur",function(h){p(h)});mejs.MediaFeatures.hasTouch||m.show()}).bind("mouseleave",function(){if(!t){d.globalUnbind(".dur");m.hide()}}); +e.addEventListener("progress",function(h){a.setProgressRail(h);a.setCurrentRail(h)},false);e.addEventListener("timeupdate",function(h){a.setProgressRail(h);a.setCurrentRail(h)},false);d.loaded=c;d.total=g;d.current=k;d.handle=j},setProgressRail:function(a){var b=a!=undefined?a.target:this.media,c=null;if(b&&b.buffered&&b.buffered.length>0&&b.buffered.end&&b.duration)c=b.buffered.end(0)/b.duration;else if(b&&b.bytesTotal!=undefined&&b.bytesTotal>0&&b.bufferedBytes!=undefined)c=b.bufferedBytes/b.bytesTotal; +else if(a&&a.lengthComputable&&a.total!=0)c=a.loaded/a.total;if(c!==null){c=Math.min(1,Math.max(0,c));this.loaded&&this.total&&this.loaded.width(this.total.width()*c)}},setCurrentRail:function(){if(this.media.currentTime!=undefined&&this.media.duration)if(this.total&&this.handle){var a=Math.round(this.total.width()*this.media.currentTime/this.media.duration),b=a-Math.round(this.handle.outerWidth(true)/2);this.current.width(a);this.handle.css("left",b)}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{duration:-1,timeAndDurationSeparator:"<span> | </span>"});f.extend(MediaElementPlayer.prototype,{buildcurrent:function(a,b,c,e){f('<div class="mejs-time"><span class="mejs-currenttime">'+(a.options.alwaysShowHours?"00:":"")+(a.options.showTimecodeFrameCount?"00:00:00":"00:00")+"</span></div>").appendTo(b);this.currenttime=this.controls.find(".mejs-currenttime");e.addEventListener("timeupdate",function(){a.updateCurrent()},false)},buildduration:function(a,b, +c,e){if(b.children().last().find(".mejs-currenttime").length>0)f(this.options.timeAndDurationSeparator+'<span class="mejs-duration">'+(this.options.duration>0?mejs.Utility.secondsToTimeCode(this.options.duration,this.options.alwaysShowHours||this.media.duration>3600,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25):(a.options.alwaysShowHours?"00:":"")+(a.options.showTimecodeFrameCount?"00:00:00":"00:00"))+"</span>").appendTo(b.find(".mejs-time"));else{b.find(".mejs-currenttime").parent().addClass("mejs-currenttime-container"); +f('<div class="mejs-time mejs-duration-container"><span class="mejs-duration">'+(this.options.duration>0?mejs.Utility.secondsToTimeCode(this.options.duration,this.options.alwaysShowHours||this.media.duration>3600,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25):(a.options.alwaysShowHours?"00:":"")+(a.options.showTimecodeFrameCount?"00:00:00":"00:00"))+"</span></div>").appendTo(b)}this.durationD=this.controls.find(".mejs-duration");e.addEventListener("timeupdate",function(){a.updateDuration()}, +false)},updateCurrent:function(){if(this.currenttime)this.currenttime.html(mejs.Utility.secondsToTimeCode(this.media.currentTime,this.options.alwaysShowHours||this.media.duration>3600,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25))},updateDuration:function(){this.container.toggleClass("mejs-long-video",this.media.duration>3600);if(this.durationD&&(this.options.duration>0||this.media.duration))this.durationD.html(mejs.Utility.secondsToTimeCode(this.options.duration>0?this.options.duration: +this.media.duration,this.options.alwaysShowHours,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25))}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{muteText:mejs.i18n.t("Mute Toggle"),hideVolumeOnTouchDevices:true,audioVolume:"horizontal",videoVolume:"vertical"});f.extend(MediaElementPlayer.prototype,{buildvolume:function(a,b,c,e){if(!((mejs.MediaFeatures.isAndroid||mejs.MediaFeatures.isiOS)&&this.options.hideVolumeOnTouchDevices)){var d=this,g=d.isVideo?d.options.videoVolume:d.options.audioVolume,k=g=="horizontal"?f('<div class="mejs-button mejs-volume-button mejs-mute"><button type="button" aria-controls="'+ +d.id+'" title="'+d.options.muteText+'" aria-label="'+d.options.muteText+'"></button></div><div class="mejs-horizontal-volume-slider"><div class="mejs-horizontal-volume-total"></div><div class="mejs-horizontal-volume-current"></div><div class="mejs-horizontal-volume-handle"></div></div>').appendTo(b):f('<div class="mejs-button mejs-volume-button mejs-mute"><button type="button" aria-controls="'+d.id+'" title="'+d.options.muteText+'" aria-label="'+d.options.muteText+'"></button><div class="mejs-volume-slider"><div class="mejs-volume-total"></div><div class="mejs-volume-current"></div><div class="mejs-volume-handle"></div></div></div>').appendTo(b), +j=d.container.find(".mejs-volume-slider, .mejs-horizontal-volume-slider"),m=d.container.find(".mejs-volume-total, .mejs-horizontal-volume-total"),q=d.container.find(".mejs-volume-current, .mejs-horizontal-volume-current"),p=d.container.find(".mejs-volume-handle, .mejs-horizontal-volume-handle"),t=function(n,o){if(!j.is(":visible")&&typeof o=="undefined"){j.show();t(n,true);j.hide()}else{n=Math.max(0,n);n=Math.min(n,1);n==0?k.removeClass("mejs-mute").addClass("mejs-unmute"):k.removeClass("mejs-unmute").addClass("mejs-mute"); +if(g=="vertical"){var s=m.height(),u=m.position(),v=s-s*n;p.css("top",Math.round(u.top+v-p.height()/2));q.height(s-v);q.css("top",u.top+v)}else{s=m.width();u=m.position();s=s*n;p.css("left",Math.round(u.left+s-p.width()/2));q.width(Math.round(s))}}},h=function(n){var o=null,s=m.offset();if(g=="vertical"){o=m.height();parseInt(m.css("top").replace(/px/,""),10);o=(o-(n.pageY-s.top))/o;if(s.top==0||s.left==0)return}else{o=m.width();o=(n.pageX-s.left)/o}o=Math.max(0,o);o=Math.min(o,1);t(o);o==0?e.setMuted(true): +e.setMuted(false);e.setVolume(o)},l=false,r=false;k.hover(function(){j.show();r=true},function(){r=false;!l&&g=="vertical"&&j.hide()});j.bind("mouseover",function(){r=true}).bind("mousedown",function(n){h(n);d.globalBind("mousemove.vol",function(o){h(o)});d.globalBind("mouseup.vol",function(){l=false;d.globalUnbind(".vol");!r&&g=="vertical"&&j.hide()});l=true;return false});k.find("button").click(function(){e.setMuted(!e.muted)});e.addEventListener("volumechange",function(){if(!l)if(e.muted){t(0); +k.removeClass("mejs-mute").addClass("mejs-unmute")}else{t(e.volume);k.removeClass("mejs-unmute").addClass("mejs-mute")}},false);if(d.container.is(":visible")){t(a.options.startVolume);a.options.startVolume===0&&e.setMuted(true);e.pluginType==="native"&&e.setVolume(a.options.startVolume)}}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{usePluginFullScreen:true,newWindowCallback:function(){return""},fullscreenText:mejs.i18n.t("Fullscreen")});f.extend(MediaElementPlayer.prototype,{isFullScreen:false,isNativeFullScreen:false,isInIframe:false,buildfullscreen:function(a,b,c,e){if(a.isVideo){a.isInIframe=window.location!=window.parent.location;mejs.MediaFeatures.hasTrueNativeFullScreen&&a.globalBind(mejs.MediaFeatures.fullScreenEventName,function(){if(a.isFullScreen)if(mejs.MediaFeatures.isFullScreen()){a.isNativeFullScreen= +true;a.setControlsSize()}else{a.isNativeFullScreen=false;a.exitFullScreen()}});var d=this,g=f('<div class="mejs-button mejs-fullscreen-button"><button type="button" aria-controls="'+d.id+'" title="'+d.options.fullscreenText+'" aria-label="'+d.options.fullscreenText+'"></button></div>').appendTo(b);if(d.media.pluginType==="native"||!d.options.usePluginFullScreen&&!mejs.MediaFeatures.isFirefox)g.click(function(){mejs.MediaFeatures.hasTrueNativeFullScreen&&mejs.MediaFeatures.isFullScreen()||a.isFullScreen? +a.exitFullScreen():a.enterFullScreen()});else{var k=null;if(function(){var h=document.createElement("x"),l=document.documentElement,r=window.getComputedStyle;if(!("pointerEvents"in h.style))return false;h.style.pointerEvents="auto";h.style.pointerEvents="x";l.appendChild(h);r=r&&r(h,"").pointerEvents==="auto";l.removeChild(h);return!!r}()&&!mejs.MediaFeatures.isOpera){var j=false,m=function(){if(j){for(var h in q)q[h].hide();g.css("pointer-events","");d.controls.css("pointer-events","");d.media.removeEventListener("click", +d.clickToPlayPauseCallback);j=false}},q={};b=["top","left","right","bottom"];var p,t=function(){var h=g.offset().left-d.container.offset().left,l=g.offset().top-d.container.offset().top,r=g.outerWidth(true),n=g.outerHeight(true),o=d.container.width(),s=d.container.height();for(p in q)q[p].css({position:"absolute",top:0,left:0});q.top.width(o).height(l);q.left.width(h).height(n).css({top:l});q.right.width(o-h-r).height(n).css({top:l,left:h+r});q.bottom.width(o).height(s-n-l).css({top:l+n})};d.globalBind("resize", +function(){t()});p=0;for(c=b.length;p<c;p++)q[b[p]]=f('<div class="mejs-fullscreen-hover" />').appendTo(d.container).mouseover(m).hide();g.on("mouseover",function(){if(!d.isFullScreen){var h=g.offset(),l=a.container.offset();e.positionFullscreenButton(h.left-l.left,h.top-l.top,false);g.css("pointer-events","none");d.controls.css("pointer-events","none");d.media.addEventListener("click",d.clickToPlayPauseCallback);for(p in q)q[p].show();t();j=true}});e.addEventListener("fullscreenchange",function(){d.isFullScreen= +!d.isFullScreen;d.isFullScreen?d.media.removeEventListener("click",d.clickToPlayPauseCallback):d.media.addEventListener("click",d.clickToPlayPauseCallback);m()});d.globalBind("mousemove",function(h){if(j){var l=g.offset();if(h.pageY<l.top||h.pageY>l.top+g.outerHeight(true)||h.pageX<l.left||h.pageX>l.left+g.outerWidth(true)){g.css("pointer-events","");d.controls.css("pointer-events","");j=false}}})}else g.on("mouseover",function(){if(k!==null){clearTimeout(k);delete k}var h=g.offset(),l=a.container.offset(); +e.positionFullscreenButton(h.left-l.left,h.top-l.top,true)}).on("mouseout",function(){if(k!==null){clearTimeout(k);delete k}k=setTimeout(function(){e.hideFullscreenButton()},1500)})}a.fullscreenBtn=g;d.globalBind("keydown",function(h){if((mejs.MediaFeatures.hasTrueNativeFullScreen&&mejs.MediaFeatures.isFullScreen()||d.isFullScreen)&&h.keyCode==27)a.exitFullScreen()})}},cleanfullscreen:function(a){a.exitFullScreen()},containerSizeTimeout:null,enterFullScreen:function(){var a=this;if(!(a.media.pluginType!== +"native"&&(mejs.MediaFeatures.isFirefox||a.options.usePluginFullScreen))){f(document.documentElement).addClass("mejs-fullscreen");normalHeight=a.container.height();normalWidth=a.container.width();if(a.media.pluginType==="native")if(mejs.MediaFeatures.hasTrueNativeFullScreen){mejs.MediaFeatures.requestFullScreen(a.container[0]);a.isInIframe&&setTimeout(function c(){if(a.isNativeFullScreen){var e=(window.devicePixelRatio||1)*f(window).width(),d=screen.width;Math.abs(d-e)>d*0.0020?a.exitFullScreen(): +setTimeout(c,500)}},500)}else if(mejs.MediaFeatures.hasSemiNativeFullScreen){a.media.webkitEnterFullscreen();return}if(a.isInIframe){var b=a.options.newWindowCallback(this);if(b!=="")if(mejs.MediaFeatures.hasTrueNativeFullScreen)setTimeout(function(){if(!a.isNativeFullScreen){a.pause();window.open(b,a.id,"top=0,left=0,width="+screen.availWidth+",height="+screen.availHeight+",resizable=yes,scrollbars=no,status=no,toolbar=no")}},250);else{a.pause();window.open(b,a.id,"top=0,left=0,width="+screen.availWidth+ +",height="+screen.availHeight+",resizable=yes,scrollbars=no,status=no,toolbar=no");return}}a.container.addClass("mejs-container-fullscreen").width("100%").height("100%");a.containerSizeTimeout=setTimeout(function(){a.container.css({width:"100%",height:"100%"});a.setControlsSize()},500);if(a.media.pluginType==="native")a.$media.width("100%").height("100%");else{a.container.find(".mejs-shim").width("100%").height("100%");a.media.setVideoSize(f(window).width(),f(window).height())}a.layers.children("div").width("100%").height("100%"); +a.fullscreenBtn&&a.fullscreenBtn.removeClass("mejs-fullscreen").addClass("mejs-unfullscreen");a.setControlsSize();a.isFullScreen=true;a.container.find(".mejs-captions-text").css("font-size",screen.width/a.width*1*100+"%");a.container.find(".mejs-captions-position").css("bottom","45px")}},exitFullScreen:function(){clearTimeout(this.containerSizeTimeout);if(this.media.pluginType!=="native"&&mejs.MediaFeatures.isFirefox)this.media.setFullscreen(false);else{if(mejs.MediaFeatures.hasTrueNativeFullScreen&& +(mejs.MediaFeatures.isFullScreen()||this.isFullScreen))mejs.MediaFeatures.cancelFullScreen();f(document.documentElement).removeClass("mejs-fullscreen");this.container.removeClass("mejs-container-fullscreen").width(normalWidth).height(normalHeight);if(this.media.pluginType==="native")this.$media.width(normalWidth).height(normalHeight);else{this.container.find(".mejs-shim").width(normalWidth).height(normalHeight);this.media.setVideoSize(normalWidth,normalHeight)}this.layers.children("div").width(normalWidth).height(normalHeight); +this.fullscreenBtn.removeClass("mejs-unfullscreen").addClass("mejs-fullscreen");this.setControlsSize();this.isFullScreen=false;this.container.find(".mejs-captions-text").css("font-size","");this.container.find(".mejs-captions-position").css("bottom","")}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{speeds:["1.50","1.25","1.00","0.75"],defaultSpeed:"1.00"});f.extend(MediaElementPlayer.prototype,{buildspeed:function(a,b,c,e){if(this.media.pluginType=="native"){c='<div class="mejs-button mejs-speed-button"><button type="button">'+this.options.defaultSpeed+'x</button><div class="mejs-speed-selector"><ul>';var d;f.inArray(this.options.defaultSpeed,this.options.speeds)===-1&&this.options.speeds.push(this.options.defaultSpeed);this.options.speeds.sort(function(g, +k){return parseFloat(k)-parseFloat(g)});for(d=0;d<this.options.speeds.length;d++){c+='<li><input type="radio" name="speed" value="'+this.options.speeds[d]+'" id="'+this.options.speeds[d]+'" ';if(this.options.speeds[d]==this.options.defaultSpeed){c+="checked=true ";c+='/><label for="'+this.options.speeds[d]+'" class="mejs-speed-selected">'+this.options.speeds[d]+"x</label></li>"}else c+='/><label for="'+this.options.speeds[d]+'">'+this.options.speeds[d]+"x</label></li>"}c+="</ul></div></div>";a.speedButton= +f(c).appendTo(b);a.playbackspeed=this.options.defaultSpeed;a.speedButton.on("click","input[type=radio]",function(){a.playbackspeed=f(this).attr("value");e.playbackRate=parseFloat(a.playbackspeed);a.speedButton.find("button").text(a.playbackspeed+"x");a.speedButton.find(".mejs-speed-selected").removeClass("mejs-speed-selected");a.speedButton.find("input[type=radio]:checked").next().addClass("mejs-speed-selected")});b=a.speedButton.find(".mejs-speed-selector");b.height(this.speedButton.find(".mejs-speed-selector ul").outerHeight(true)+ +a.speedButton.find(".mejs-speed-translations").outerHeight(true));b.css("top",-1*b.height()+"px")}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{startLanguage:"",tracksText:mejs.i18n.t("Captions/Subtitles"),hideCaptionsButtonWhenEmpty:true,toggleCaptionsButtonWhenOnlyOne:false,slidesSelector:""});f.extend(MediaElementPlayer.prototype,{hasChapters:false,buildtracks:function(a,b,c,e){if(a.tracks.length!==0){var d;if(this.domNode.textTracks)for(d=this.domNode.textTracks.length-1;d>=0;d--)this.domNode.textTracks[d].mode="hidden";a.chapters=f('<div class="mejs-chapters mejs-layer"></div>').prependTo(c).hide(); +a.captions=f('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover"><span class="mejs-captions-text"></span></div></div>').prependTo(c).hide();a.captionsText=a.captions.find(".mejs-captions-text");a.captionsButton=f('<div class="mejs-button mejs-captions-button"><button type="button" aria-controls="'+this.id+'" title="'+this.options.tracksText+'" aria-label="'+this.options.tracksText+'"></button><div class="mejs-captions-selector"><ul><li><input type="radio" name="'+ +a.id+'_captions" id="'+a.id+'_captions_none" value="none" checked="checked" /><label for="'+a.id+'_captions_none">'+mejs.i18n.t("None")+"</label></li></ul></div></div>").appendTo(b);for(d=b=0;d<a.tracks.length;d++)a.tracks[d].kind=="subtitles"&&b++;if(this.options.toggleCaptionsButtonWhenOnlyOne&&b==1)a.captionsButton.on("click",function(){lang=a.selectedTrack===null?a.tracks[0].srclang:"none";a.setTrack(lang)});else{a.captionsButton.on("mouseenter focusin",function(){f(this).find(".mejs-captions-selector").css("visibility", +"visible")}).on("click","input[type=radio]",function(){lang=this.value;a.setTrack(lang)});a.captionsButton.on("mouseleave focusout",function(){f(this).find(".mejs-captions-selector").css("visibility","hidden")})}a.options.alwaysShowControls?a.container.find(".mejs-captions-position").addClass("mejs-captions-position-hover"):a.container.bind("controlsshown",function(){a.container.find(".mejs-captions-position").addClass("mejs-captions-position-hover")}).bind("controlshidden",function(){e.paused||a.container.find(".mejs-captions-position").removeClass("mejs-captions-position-hover")}); +a.trackToLoad=-1;a.selectedTrack=null;a.isLoadingTrack=false;for(d=0;d<a.tracks.length;d++)a.tracks[d].kind=="subtitles"&&a.addTrackButton(a.tracks[d].srclang,a.tracks[d].label);a.loadNextTrack();e.addEventListener("timeupdate",function(){a.displayCaptions()},false);if(a.options.slidesSelector!==""){a.slidesContainer=f(a.options.slidesSelector);e.addEventListener("timeupdate",function(){a.displaySlides()},false)}e.addEventListener("loadedmetadata",function(){a.displayChapters()},false);a.container.hover(function(){if(a.hasChapters){a.chapters.css("visibility", +"visible");a.chapters.fadeIn(200).height(a.chapters.find(".mejs-chapter").outerHeight())}},function(){a.hasChapters&&!e.paused&&a.chapters.fadeOut(200,function(){f(this).css("visibility","hidden");f(this).css("display","block")})});a.node.getAttribute("autoplay")!==null&&a.chapters.css("visibility","hidden")}},setTrack:function(a){var b;if(a=="none"){this.selectedTrack=null;this.captionsButton.removeClass("mejs-captions-enabled")}else for(b=0;b<this.tracks.length;b++)if(this.tracks[b].srclang==a){this.selectedTrack=== +null&&this.captionsButton.addClass("mejs-captions-enabled");this.selectedTrack=this.tracks[b];this.captions.attr("lang",this.selectedTrack.srclang);this.displayCaptions();break}},loadNextTrack:function(){this.trackToLoad++;if(this.trackToLoad<this.tracks.length){this.isLoadingTrack=true;this.loadTrack(this.trackToLoad)}else{this.isLoadingTrack=false;this.checkForTracks()}},loadTrack:function(a){var b=this,c=b.tracks[a];f.ajax({url:c.src,dataType:"text",success:function(e){c.entries=typeof e=="string"&& +/<tt\s+xml/ig.exec(e)?mejs.TrackFormatParser.dfxp.parse(e):mejs.TrackFormatParser.webvtt.parse(e);c.isLoaded=true;b.enableTrackButton(c.srclang,c.label);b.loadNextTrack();c.kind=="chapters"&&b.media.addEventListener("play",function(){b.media.duration>0&&b.displayChapters(c)},false);c.kind=="slides"&&b.setupSlides(c)},error:function(){b.loadNextTrack()}})},enableTrackButton:function(a,b){if(b==="")b=mejs.language.codes[a]||a;this.captionsButton.find("input[value="+a+"]").prop("disabled",false).siblings("label").html(b); +this.options.startLanguage==a&&f("#"+this.id+"_captions_"+a).prop("checked",true).trigger("click");this.adjustLanguageBox()},addTrackButton:function(a,b){if(b==="")b=mejs.language.codes[a]||a;this.captionsButton.find("ul").append(f('<li><input type="radio" name="'+this.id+'_captions" id="'+this.id+"_captions_"+a+'" value="'+a+'" disabled="disabled" /><label for="'+this.id+"_captions_"+a+'">'+b+" (loading)</label></li>"));this.adjustLanguageBox();this.container.find(".mejs-captions-translations option[value="+ +a+"]").remove()},adjustLanguageBox:function(){this.captionsButton.find(".mejs-captions-selector").height(this.captionsButton.find(".mejs-captions-selector ul").outerHeight(true)+this.captionsButton.find(".mejs-captions-translations").outerHeight(true))},checkForTracks:function(){var a=false;if(this.options.hideCaptionsButtonWhenEmpty){for(i=0;i<this.tracks.length;i++)if(this.tracks[i].kind=="subtitles"){a=true;break}if(!a){this.captionsButton.hide();this.setControlsSize()}}},displayCaptions:function(){if(typeof this.tracks!= +"undefined"){var a,b=this.selectedTrack;if(b!==null&&b.isLoaded)for(a=0;a<b.entries.times.length;a++)if(this.media.currentTime>=b.entries.times[a].start&&this.media.currentTime<=b.entries.times[a].stop){this.captionsText.html(b.entries.text[a]).attr("class","mejs-captions-text "+(b.entries.times[a].identifier||""));this.captions.show().height(0);return}this.captions.hide()}},setupSlides:function(a){this.slides=a;this.slides.entries.imgs=[this.slides.entries.text.length];this.showSlide(0)},showSlide:function(a){if(!(typeof this.tracks== +"undefined"||typeof this.slidesContainer=="undefined")){var b=this,c=b.slides.entries.text[a],e=b.slides.entries.imgs[a];if(typeof e=="undefined"||typeof e.fadeIn=="undefined")b.slides.entries.imgs[a]=e=f('<img src="'+c+'">').on("load",function(){e.appendTo(b.slidesContainer).hide().fadeIn().siblings(":visible").fadeOut()});else!e.is(":visible")&&!e.is(":animated")&&e.fadeIn().siblings(":visible").fadeOut()}},displaySlides:function(){if(typeof this.slides!="undefined"){var a=this.slides,b;for(b=0;b< +a.entries.times.length;b++)if(this.media.currentTime>=a.entries.times[b].start&&this.media.currentTime<=a.entries.times[b].stop){this.showSlide(b);break}}},displayChapters:function(){var a;for(a=0;a<this.tracks.length;a++)if(this.tracks[a].kind=="chapters"&&this.tracks[a].isLoaded){this.drawChapters(this.tracks[a]);this.hasChapters=true;break}},drawChapters:function(a){var b=this,c,e,d=e=0;b.chapters.empty();for(c=0;c<a.entries.times.length;c++){e=a.entries.times[c].stop-a.entries.times[c].start; +e=Math.floor(e/b.media.duration*100);if(e+d>100||c==a.entries.times.length-1&&e+d<100)e=100-d;b.chapters.append(f('<div class="mejs-chapter" rel="'+a.entries.times[c].start+'" style="left: '+d.toString()+"%;width: "+e.toString()+'%;"><div class="mejs-chapter-block'+(c==a.entries.times.length-1?" mejs-chapter-block-last":"")+'"><span class="ch-title">'+a.entries.text[c]+'</span><span class="ch-time">'+mejs.Utility.secondsToTimeCode(a.entries.times[c].start)+"–"+mejs.Utility.secondsToTimeCode(a.entries.times[c].stop)+ +"</span></div></div>"));d+=e}b.chapters.find("div.mejs-chapter").click(function(){b.media.setCurrentTime(parseFloat(f(this).attr("rel")));b.media.paused&&b.media.play()});b.chapters.show()}});mejs.language={codes:{af:"Afrikaans",sq:"Albanian",ar:"Arabic",be:"Belarusian",bg:"Bulgarian",ca:"Catalan",zh:"Chinese","zh-cn":"Chinese Simplified","zh-tw":"Chinese Traditional",hr:"Croatian",cs:"Czech",da:"Danish",nl:"Dutch",en:"English",et:"Estonian",fl:"Filipino",fi:"Finnish",fr:"French",gl:"Galician",de:"German", +el:"Greek",ht:"Haitian Creole",iw:"Hebrew",hi:"Hindi",hu:"Hungarian",is:"Icelandic",id:"Indonesian",ga:"Irish",it:"Italian",ja:"Japanese",ko:"Korean",lv:"Latvian",lt:"Lithuanian",mk:"Macedonian",ms:"Malay",mt:"Maltese",no:"Norwegian",fa:"Persian",pl:"Polish",pt:"Portuguese",ro:"Romanian",ru:"Russian",sr:"Serbian",sk:"Slovak",sl:"Slovenian",es:"Spanish",sw:"Swahili",sv:"Swedish",tl:"Tagalog",th:"Thai",tr:"Turkish",uk:"Ukrainian",vi:"Vietnamese",cy:"Welsh",yi:"Yiddish"}};mejs.TrackFormatParser={webvtt:{pattern_timecode:/^((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, +parse:function(a){var b=0;a=mejs.TrackFormatParser.split2(a,/\r?\n/);for(var c={text:[],times:[]},e,d,g;b<a.length;b++){if((e=this.pattern_timecode.exec(a[b]))&&b<a.length){if(b-1>=0&&a[b-1]!=="")g=a[b-1];b++;d=a[b];for(b++;a[b]!==""&&b<a.length;){d=d+"\n"+a[b];b++}d=f.trim(d).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig,"<a href='$1' target='_blank'>$1</a>");c.text.push(d);c.times.push({identifier:g,start:mejs.Utility.convertSMPTEtoSeconds(e[1])===0?0.2:mejs.Utility.convertSMPTEtoSeconds(e[1]), +stop:mejs.Utility.convertSMPTEtoSeconds(e[3]),settings:e[5]})}g=""}return c}},dfxp:{parse:function(a){a=f(a).filter("tt");var b=0;b=a.children("div").eq(0);var c=b.find("p");b=a.find("#"+b.attr("style"));var e,d;a={text:[],times:[]};if(b.length){d=b.removeAttr("id").get(0).attributes;if(d.length){e={};for(b=0;b<d.length;b++)e[d[b].name.split(":")[1]]=d[b].value}}for(b=0;b<c.length;b++){var g;d={start:null,stop:null,style:null};if(c.eq(b).attr("begin"))d.start=mejs.Utility.convertSMPTEtoSeconds(c.eq(b).attr("begin")); +if(!d.start&&c.eq(b-1).attr("end"))d.start=mejs.Utility.convertSMPTEtoSeconds(c.eq(b-1).attr("end"));if(c.eq(b).attr("end"))d.stop=mejs.Utility.convertSMPTEtoSeconds(c.eq(b).attr("end"));if(!d.stop&&c.eq(b+1).attr("begin"))d.stop=mejs.Utility.convertSMPTEtoSeconds(c.eq(b+1).attr("begin"));if(e){g="";for(var k in e)g+=k+":"+e[k]+";"}if(g)d.style=g;if(d.start===0)d.start=0.2;a.times.push(d);d=f.trim(c.eq(b).html()).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, +"<a href='$1' target='_blank'>$1</a>");a.text.push(d);if(a.times.start===0)a.times.start=2}return a}},split2:function(a,b){return a.split(b)}};if("x\n\ny".split(/\n/gi).length!=3)mejs.TrackFormatParser.split2=function(a,b){var c=[],e="",d;for(d=0;d<a.length;d++){e+=a.substring(d,d+1);if(b.test(e)){c.push(e.replace(b,""));e=""}}c.push(e);return c}})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{contextMenuItems:[{render:function(a){if(typeof a.enterFullScreen=="undefined")return null;return a.isFullScreen?mejs.i18n.t("Turn off Fullscreen"):mejs.i18n.t("Go Fullscreen")},click:function(a){a.isFullScreen?a.exitFullScreen():a.enterFullScreen()}},{render:function(a){return a.media.muted?mejs.i18n.t("Unmute"):mejs.i18n.t("Mute")},click:function(a){a.media.muted?a.setMuted(false):a.setMuted(true)}},{isSeparator:true},{render:function(){return mejs.i18n.t("Download Video")}, +click:function(a){window.location.href=a.media.currentSrc}}]});f.extend(MediaElementPlayer.prototype,{buildcontextmenu:function(a){a.contextMenu=f('<div class="mejs-contextmenu"></div>').appendTo(f("body")).hide();a.container.bind("contextmenu",function(b){if(a.isContextMenuEnabled){b.preventDefault();a.renderContextMenu(b.clientX-1,b.clientY-1);return false}});a.container.bind("click",function(){a.contextMenu.hide()});a.contextMenu.bind("mouseleave",function(){a.startContextMenuTimer()})},cleancontextmenu:function(a){a.contextMenu.remove()}, +isContextMenuEnabled:true,enableContextMenu:function(){this.isContextMenuEnabled=true},disableContextMenu:function(){this.isContextMenuEnabled=false},contextMenuTimeout:null,startContextMenuTimer:function(){var a=this;a.killContextMenuTimer();a.contextMenuTimer=setTimeout(function(){a.hideContextMenu();a.killContextMenuTimer()},750)},killContextMenuTimer:function(){var a=this.contextMenuTimer;if(a!=null){clearTimeout(a);delete a}},hideContextMenu:function(){this.contextMenu.hide()},renderContextMenu:function(a, +b){for(var c=this,e="",d=c.options.contextMenuItems,g=0,k=d.length;g<k;g++)if(d[g].isSeparator)e+='<div class="mejs-contextmenu-separator"></div>';else{var j=d[g].render(c);if(j!=null)e+='<div class="mejs-contextmenu-item" data-itemindex="'+g+'" id="element-'+Math.random()*1E6+'">'+j+"</div>"}c.contextMenu.empty().append(f(e)).css({top:b,left:a}).show();c.contextMenu.find(".mejs-contextmenu-item").each(function(){var m=f(this),q=parseInt(m.data("itemindex"),10),p=c.options.contextMenuItems[q];typeof p.show!= +"undefined"&&p.show(m,c);m.click(function(){typeof p.click!="undefined"&&p.click(c);c.contextMenu.hide()})});setTimeout(function(){c.killControlsTimer("rev3")},100)}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{postrollCloseText:mejs.i18n.t("Close")});f.extend(MediaElementPlayer.prototype,{buildpostroll:function(a,b,c){var e=this.container.find('link[rel="postroll"]').attr("href");if(typeof e!=="undefined"){a.postroll=f('<div class="mejs-postroll-layer mejs-layer"><a class="mejs-postroll-close" onclick="$(this).parent().hide();return false;">'+this.options.postrollCloseText+'</a><div class="mejs-postroll-layer-content"></div></div>').prependTo(c).hide();this.media.addEventListener("ended", +function(){f.ajax({dataType:"html",url:e,success:function(d){c.find(".mejs-postroll-layer-content").html(d)}});a.postroll.show()},false)}}})})(mejs.$); + diff --git a/js/mediaelement/build/mediaelement.js b/js/mediaelement/build/mediaelement.js new file mode 100644 index 0000000000000000000000000000000000000000..5d4ee742af9b4d241164bee1d3144d0a9a392e2e --- /dev/null +++ b/js/mediaelement/build/mediaelement.js @@ -0,0 +1,1961 @@ +/*! +* MediaElement.js +* HTML5 <video> and <audio> shim and player +* http://mediaelementjs.com/ +* +* Creates a JavaScript object that mimics HTML5 MediaElement API +* for browsers that don't understand HTML5 or can't play the provided codec +* Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3 +* +* Copyright 2010-2014, John Dyer (http://j.hn) +* License: MIT +* +*/ +// Namespace +var mejs = mejs || {}; + +// version number +mejs.version = '2.15.1'; + + +// player number (for missing, same id attr) +mejs.meIndex = 0; + +// media types accepted by plugins +mejs.plugins = { + silverlight: [ + {version: [3,0], types: ['video/mp4','video/m4v','video/mov','video/wmv','audio/wma','audio/m4a','audio/mp3','audio/wav','audio/mpeg']} + ], + flash: [ + {version: [9,0,124], types: ['video/mp4','video/m4v','video/mov','video/flv','video/rtmp','video/x-flv','audio/flv','audio/x-flv','audio/mp3','audio/m4a','audio/mpeg', 'video/youtube', 'video/x-youtube', 'application/x-mpegURL']} + //,{version: [12,0], types: ['video/webm']} // for future reference (hopefully!) + ], + youtube: [ + {version: null, types: ['video/youtube', 'video/x-youtube', 'audio/youtube', 'audio/x-youtube']} + ], + vimeo: [ + {version: null, types: ['video/vimeo', 'video/x-vimeo']} + ] +}; + +/* +Utility methods +*/ +mejs.Utility = { + encodeUrl: function(url) { + return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26'); + }, + escapeHTML: function(s) { + return s.toString().split('&').join('&').split('<').join('<').split('"').join('"'); + }, + absolutizeUrl: function(url) { + var el = document.createElement('div'); + el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>'; + return el.firstChild.href; + }, + getScriptPath: function(scriptNames) { + var + i = 0, + j, + codePath = '', + testname = '', + slashPos, + filenamePos, + scriptUrl, + scriptPath, + scriptFilename, + scripts = document.getElementsByTagName('script'), + il = scripts.length, + jl = scriptNames.length; + + // go through all <script> tags + for (; i < il; i++) { + scriptUrl = scripts[i].src; + slashPos = scriptUrl.lastIndexOf('/'); + if (slashPos > -1) { + scriptFilename = scriptUrl.substring(slashPos + 1); + scriptPath = scriptUrl.substring(0, slashPos + 1); + } else { + scriptFilename = scriptUrl; + scriptPath = ''; + } + + // see if any <script> tags have a file name that matches the + for (j = 0; j < jl; j++) { + testname = scriptNames[j]; + filenamePos = scriptFilename.indexOf(testname); + if (filenamePos > -1) { + codePath = scriptPath; + break; + } + } + + // if we found a path, then break and return it + if (codePath !== '') { + break; + } + } + + // send the best path back + return codePath; + }, + secondsToTimeCode: function(time, forceHours, showFrameCount, fps) { + //add framecount + if (typeof showFrameCount == 'undefined') { + showFrameCount=false; + } else if(typeof fps == 'undefined') { + fps = 25; + } + + var hours = Math.floor(time / 3600) % 24, + minutes = Math.floor(time / 60) % 60, + seconds = Math.floor(time % 60), + frames = Math.floor(((time % 1)*fps).toFixed(3)), + result = + ( (forceHours || hours > 0) ? (hours < 10 ? '0' + hours : hours) + ':' : '') + + (minutes < 10 ? '0' + minutes : minutes) + ':' + + (seconds < 10 ? '0' + seconds : seconds) + + ((showFrameCount) ? ':' + (frames < 10 ? '0' + frames : frames) : ''); + + return result; + }, + + timeCodeToSeconds: function(hh_mm_ss_ff, forceHours, showFrameCount, fps){ + if (typeof showFrameCount == 'undefined') { + showFrameCount=false; + } else if(typeof fps == 'undefined') { + fps = 25; + } + + var tc_array = hh_mm_ss_ff.split(":"), + tc_hh = parseInt(tc_array[0], 10), + tc_mm = parseInt(tc_array[1], 10), + tc_ss = parseInt(tc_array[2], 10), + tc_ff = 0, + tc_in_seconds = 0; + + if (showFrameCount) { + tc_ff = parseInt(tc_array[3])/fps; + } + + tc_in_seconds = ( tc_hh * 3600 ) + ( tc_mm * 60 ) + tc_ss + tc_ff; + + return tc_in_seconds; + }, + + + convertSMPTEtoSeconds: function (SMPTE) { + if (typeof SMPTE != 'string') + return false; + + SMPTE = SMPTE.replace(',', '.'); + + var secs = 0, + decimalLen = (SMPTE.indexOf('.') != -1) ? SMPTE.split('.')[1].length : 0, + multiplier = 1; + + SMPTE = SMPTE.split(':').reverse(); + + for (var i = 0; i < SMPTE.length; i++) { + multiplier = 1; + if (i > 0) { + multiplier = Math.pow(60, i); + } + secs += Number(SMPTE[i]) * multiplier; + } + return Number(secs.toFixed(decimalLen)); + }, + + /* borrowed from SWFObject: http://code.google.com/p/swfobject/source/browse/trunk/swfobject/src/swfobject.js#474 */ + removeSwf: function(id) { + var obj = document.getElementById(id); + if (obj && /object|embed/i.test(obj.nodeName)) { + if (mejs.MediaFeatures.isIE) { + obj.style.display = "none"; + (function(){ + if (obj.readyState == 4) { + mejs.Utility.removeObjectInIE(id); + } else { + setTimeout(arguments.callee, 10); + } + })(); + } else { + obj.parentNode.removeChild(obj); + } + } + }, + removeObjectInIE: function(id) { + var obj = document.getElementById(id); + if (obj) { + for (var i in obj) { + if (typeof obj[i] == "function") { + obj[i] = null; + } + } + obj.parentNode.removeChild(obj); + } + } +}; + + +// Core detector, plugins are added below +mejs.PluginDetector = { + + // main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]); + hasPluginVersion: function(plugin, v) { + var pv = this.plugins[plugin]; + v[1] = v[1] || 0; + v[2] = v[2] || 0; + return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; + }, + + // cached values + nav: window.navigator, + ua: window.navigator.userAgent.toLowerCase(), + + // stored version numbers + plugins: [], + + // runs detectPlugin() and stores the version number + addPlugin: function(p, pluginName, mimeType, activeX, axDetect) { + this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect); + }, + + // get the version number from the mimetype (all but IE) or ActiveX (IE) + detectPlugin: function(pluginName, mimeType, activeX, axDetect) { + + var version = [0,0,0], + description, + i, + ax; + + // Firefox, Webkit, Opera + if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') { + description = this.nav.plugins[pluginName].description; + if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) { + version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.'); + for (i=0; i<version.length; i++) { + version[i] = parseInt(version[i].match(/\d+/), 10); + } + } + // Internet Explorer / ActiveX + } else if (typeof(window.ActiveXObject) != 'undefined') { + try { + ax = new ActiveXObject(activeX); + if (ax) { + version = axDetect(ax); + } + } + catch (e) { } + } + return version; + } +}; + +// Add Flash detection +mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) { + // adapted from SWFObject + var version = [], + d = ax.GetVariable("$version"); + if (d) { + d = d.split(" ")[1].split(","); + version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + return version; +}); + +// Add Silverlight detection +mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) { + // Silverlight cannot report its version number to IE + // but it does have a isVersionSupported function, so we have to loop through it to get a version number. + // adapted from http://www.silverlightversion.com/ + var v = [0,0,0,0], + loopMatch = function(ax, v, i, n) { + while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){ + v[i]+=n; + } + v[i] -= n; + }; + loopMatch(ax, v, 0, 1); + loopMatch(ax, v, 1, 1); + loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx) + loopMatch(ax, v, 2, 1000); + loopMatch(ax, v, 2, 100); + loopMatch(ax, v, 2, 10); + loopMatch(ax, v, 2, 1); + loopMatch(ax, v, 3, 1); + + return v; +}); +// add adobe acrobat +/* +PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) { + var version = [], + d = ax.GetVersions().split(',')[0].split('=')[1].split('.'); + + if (d) { + version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + return version; +}); +*/ +// necessary detection (fixes for <IE9) +mejs.MediaFeatures = { + init: function() { + var + t = this, + d = document, + nav = mejs.PluginDetector.nav, + ua = mejs.PluginDetector.ua.toLowerCase(), + i, + v, + html5Elements = ['source','track','audio','video']; + + // detect browsers (only the ones that have some kind of quirk we need to work around) + t.isiPad = (ua.match(/ipad/i) !== null); + t.isiPhone = (ua.match(/iphone/i) !== null); + t.isiOS = t.isiPhone || t.isiPad; + t.isAndroid = (ua.match(/android/i) !== null); + t.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null); + t.isBustedNativeHTTPS = (location.protocol === 'https:' && (ua.match(/android [12]\./) !== null || ua.match(/macintosh.* version.* safari/) !== null)); + t.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1 || nav.appName.toLowerCase().match(/trident/gi) !== null); + t.isChrome = (ua.match(/chrome/gi) !== null); + t.isChromium = (ua.match(/chromium/gi) !== null); + t.isFirefox = (ua.match(/firefox/gi) !== null); + t.isWebkit = (ua.match(/webkit/gi) !== null); + t.isGecko = (ua.match(/gecko/gi) !== null) && !t.isWebkit && !t.isIE; + t.isOpera = (ua.match(/opera/gi) !== null); + t.hasTouch = ('ontouchstart' in window); // && window.ontouchstart != null); // this breaks iOS 7 + + // borrowed from Modernizr + t.svg = !! document.createElementNS && + !! document.createElementNS('http://www.w3.org/2000/svg','svg').createSVGRect; + + // create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection + for (i=0; i<html5Elements.length; i++) { + v = document.createElement(html5Elements[i]); + } + + t.supportsMediaTag = (typeof v.canPlayType !== 'undefined' || t.isBustedAndroid); + + // Fix for IE9 on Windows 7N / Windows 7KN (Media Player not installer) + try{ + v.canPlayType("video/mp4"); + }catch(e){ + t.supportsMediaTag = false; + } + + // detect native JavaScript fullscreen (Safari/Firefox only, Chrome still fails) + + // iOS + t.hasSemiNativeFullScreen = (typeof v.webkitEnterFullscreen !== 'undefined'); + + // W3C + t.hasNativeFullscreen = (typeof v.requestFullscreen !== 'undefined'); + + // webkit/firefox/IE11+ + t.hasWebkitNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined'); + t.hasMozNativeFullScreen = (typeof v.mozRequestFullScreen !== 'undefined'); + t.hasMsNativeFullScreen = (typeof v.msRequestFullscreen !== 'undefined'); + + t.hasTrueNativeFullScreen = (t.hasWebkitNativeFullScreen || t.hasMozNativeFullScreen || t.hasMsNativeFullScreen); + t.nativeFullScreenEnabled = t.hasTrueNativeFullScreen; + + // Enabled? + if (t.hasMozNativeFullScreen) { + t.nativeFullScreenEnabled = document.mozFullScreenEnabled; + } else if (t.hasMsNativeFullScreen) { + t.nativeFullScreenEnabled = document.msFullscreenEnabled; + } + + if (t.isChrome) { + t.hasSemiNativeFullScreen = false; + } + + if (t.hasTrueNativeFullScreen) { + + t.fullScreenEventName = ''; + if (t.hasWebkitNativeFullScreen) { + t.fullScreenEventName = 'webkitfullscreenchange'; + + } else if (t.hasMozNativeFullScreen) { + t.fullScreenEventName = 'mozfullscreenchange'; + + } else if (t.hasMsNativeFullScreen) { + t.fullScreenEventName = 'MSFullscreenChange'; + } + + t.isFullScreen = function() { + if (t.hasMozNativeFullScreen) { + return d.mozFullScreen; + + } else if (t.hasWebkitNativeFullScreen) { + return d.webkitIsFullScreen; + + } else if (t.hasMsNativeFullScreen) { + return d.msFullscreenElement !== null; + } + } + + t.requestFullScreen = function(el) { + + if (t.hasWebkitNativeFullScreen) { + el.webkitRequestFullScreen(); + + } else if (t.hasMozNativeFullScreen) { + el.mozRequestFullScreen(); + + } else if (t.hasMsNativeFullScreen) { + el.msRequestFullscreen(); + + } + } + + t.cancelFullScreen = function() { + if (t.hasWebkitNativeFullScreen) { + document.webkitCancelFullScreen(); + + } else if (t.hasMozNativeFullScreen) { + document.mozCancelFullScreen(); + + } else if (t.hasMsNativeFullScreen) { + document.msExitFullscreen(); + + } + } + + } + + + // OS X 10.5 can't do this even if it says it can :( + if (t.hasSemiNativeFullScreen && ua.match(/mac os x 10_5/i)) { + t.hasNativeFullScreen = false; + t.hasSemiNativeFullScreen = false; + } + + } +}; +mejs.MediaFeatures.init(); + +/* +extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below) +*/ +mejs.HtmlMediaElement = { + pluginType: 'native', + isFullScreen: false, + + setCurrentTime: function (time) { + this.currentTime = time; + }, + + setMuted: function (muted) { + this.muted = muted; + }, + + setVolume: function (volume) { + this.volume = volume; + }, + + // for parity with the plugin versions + stop: function () { + this.pause(); + }, + + // This can be a url string + // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] + setSrc: function (url) { + + // Fix for IE9 which can't set .src when there are <source> elements. Awesome, right? + var + existingSources = this.getElementsByTagName('source'); + while (existingSources.length > 0){ + this.removeChild(existingSources[0]); + } + + if (typeof url == 'string') { + this.src = url; + } else { + var i, media; + + for (i=0; i<url.length; i++) { + media = url[i]; + if (this.canPlayType(media.type)) { + this.src = media.src; + break; + } + } + } + }, + + setVideoSize: function (width, height) { + this.width = width; + this.height = height; + } +}; + +/* +Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember] +*/ +mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) { + this.id = pluginid; + this.pluginType = pluginType; + this.src = mediaUrl; + this.events = {}; + this.attributes = {}; +}; + +// JavaScript values and ExternalInterface methods that match HTML5 video properties methods +// http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html +// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html +mejs.PluginMediaElement.prototype = { + + // special + pluginElement: null, + pluginType: '', + isFullScreen: false, + + // not implemented :( + playbackRate: -1, + defaultPlaybackRate: -1, + seekable: [], + played: [], + + // HTML5 read-only properties + paused: true, + ended: false, + seeking: false, + duration: 0, + error: null, + tagName: '', + + // HTML5 get/set properties, but only set (updated by event handlers) + muted: false, + volume: 1, + currentTime: 0, + + // HTML5 methods + play: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.playVideo(); + } else { + this.pluginApi.playMedia(); + } + this.paused = false; + } + }, + load: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + } else { + this.pluginApi.loadMedia(); + } + + this.paused = false; + } + }, + pause: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.pauseVideo(); + } else { + this.pluginApi.pauseMedia(); + } + + + this.paused = true; + } + }, + stop: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.stopVideo(); + } else { + this.pluginApi.stopMedia(); + } + this.paused = true; + } + }, + canPlayType: function(type) { + var i, + j, + pluginInfo, + pluginVersions = mejs.plugins[this.pluginType]; + + for (i=0; i<pluginVersions.length; i++) { + pluginInfo = pluginVersions[i]; + + // test if user has the correct plugin version + if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) { + + // test for plugin playback types + for (j=0; j<pluginInfo.types.length; j++) { + // find plugin that can play the type + if (type == pluginInfo.types[j]) { + return 'probably'; + } + } + } + } + + return ''; + }, + + positionFullscreenButton: function(x,y,visibleAndAbove) { + if (this.pluginApi != null && this.pluginApi.positionFullscreenButton) { + this.pluginApi.positionFullscreenButton(Math.floor(x),Math.floor(y),visibleAndAbove); + } + }, + + hideFullscreenButton: function() { + if (this.pluginApi != null && this.pluginApi.hideFullscreenButton) { + this.pluginApi.hideFullscreenButton(); + } + }, + + + // custom methods since not all JavaScript implementations support get/set + + // This can be a url string + // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] + setSrc: function (url) { + if (typeof url == 'string') { + this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url)); + this.src = mejs.Utility.absolutizeUrl(url); + } else { + var i, media; + + for (i=0; i<url.length; i++) { + media = url[i]; + if (this.canPlayType(media.type)) { + this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src)); + this.src = mejs.Utility.absolutizeUrl(url); + break; + } + } + } + + }, + setCurrentTime: function (time) { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.seekTo(time); + } else { + this.pluginApi.setCurrentTime(time); + } + + + + this.currentTime = time; + } + }, + setVolume: function (volume) { + if (this.pluginApi != null) { + // same on YouTube and MEjs + if (this.pluginType == 'youtube') { + this.pluginApi.setVolume(volume * 100); + } else { + this.pluginApi.setVolume(volume); + } + this.volume = volume; + } + }, + setMuted: function (muted) { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube') { + if (muted) { + this.pluginApi.mute(); + } else { + this.pluginApi.unMute(); + } + this.muted = muted; + this.dispatchEvent('volumechange'); + } else { + this.pluginApi.setMuted(muted); + } + this.muted = muted; + } + }, + + // additional non-HTML5 methods + setVideoSize: function (width, height) { + + //if (this.pluginType == 'flash' || this.pluginType == 'silverlight') { + if (this.pluginElement && this.pluginElement.style) { + this.pluginElement.style.width = width + 'px'; + this.pluginElement.style.height = height + 'px'; + } + if (this.pluginApi != null && this.pluginApi.setVideoSize) { + this.pluginApi.setVideoSize(width, height); + } + //} + }, + + setFullscreen: function (fullscreen) { + if (this.pluginApi != null && this.pluginApi.setFullscreen) { + this.pluginApi.setFullscreen(fullscreen); + } + }, + + enterFullScreen: function() { + if (this.pluginApi != null && this.pluginApi.setFullscreen) { + this.setFullscreen(true); + } + + }, + + exitFullScreen: function() { + if (this.pluginApi != null && this.pluginApi.setFullscreen) { + this.setFullscreen(false); + } + }, + + // start: fake events + addEventListener: function (eventName, callback, bubble) { + this.events[eventName] = this.events[eventName] || []; + this.events[eventName].push(callback); + }, + removeEventListener: function (eventName, callback) { + if (!eventName) { this.events = {}; return true; } + var callbacks = this.events[eventName]; + if (!callbacks) return true; + if (!callback) { this.events[eventName] = []; return true; } + for (var i = 0; i < callbacks.length; i++) { + if (callbacks[i] === callback) { + this.events[eventName].splice(i, 1); + return true; + } + } + return false; + }, + dispatchEvent: function (eventName) { + var i, + args, + callbacks = this.events[eventName]; + + if (callbacks) { + args = Array.prototype.slice.call(arguments, 1); + for (i = 0; i < callbacks.length; i++) { + callbacks[i].apply(null, args); + } + } + }, + // end: fake events + + // fake DOM attribute methods + hasAttribute: function(name){ + return (name in this.attributes); + }, + removeAttribute: function(name){ + delete this.attributes[name]; + }, + getAttribute: function(name){ + if (this.hasAttribute(name)) { + return this.attributes[name]; + } + return ''; + }, + setAttribute: function(name, value){ + this.attributes[name] = value; + }, + + remove: function() { + mejs.Utility.removeSwf(this.pluginElement.id); + mejs.MediaPluginBridge.unregisterPluginElement(this.pluginElement.id); + } +}; + +// Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties +mejs.MediaPluginBridge = { + + pluginMediaElements:{}, + htmlMediaElements:{}, + + registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) { + this.pluginMediaElements[id] = pluginMediaElement; + this.htmlMediaElements[id] = htmlMediaElement; + }, + + unregisterPluginElement: function (id) { + delete this.pluginMediaElements[id]; + delete this.htmlMediaElements[id]; + }, + + // when Flash/Silverlight is ready, it calls out to this method + initPlugin: function (id) { + + var pluginMediaElement = this.pluginMediaElements[id], + htmlMediaElement = this.htmlMediaElements[id]; + + if (pluginMediaElement) { + // find the javascript bridge + switch (pluginMediaElement.pluginType) { + case "flash": + pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id); + break; + case "silverlight": + pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id); + pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS; + break; + } + + if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) { + pluginMediaElement.success(pluginMediaElement, htmlMediaElement); + } + } + }, + + // receives events from Flash/Silverlight and sends them out as HTML5 media events + // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html + fireEvent: function (id, eventName, values) { + + var + e, + i, + bufferedTime, + pluginMediaElement = this.pluginMediaElements[id]; + + if(!pluginMediaElement){ + return; + } + + // fake event object to mimic real HTML media event. + e = { + type: eventName, + target: pluginMediaElement + }; + + // attach all values to element and event object + for (i in values) { + pluginMediaElement[i] = values[i]; + e[i] = values[i]; + } + + // fake the newer W3C buffered TimeRange (loaded and total have been removed) + bufferedTime = values.bufferedTime || 0; + + e.target.buffered = e.buffered = { + start: function(index) { + return 0; + }, + end: function (index) { + return bufferedTime; + }, + length: 1 + }; + + pluginMediaElement.dispatchEvent(e.type, e); + } +}; + +/* +Default options +*/ +mejs.MediaElementDefaults = { + // allows testing on HTML5, flash, silverlight + // auto: attempts to detect what the browser can do + // auto_plugin: prefer plugins and then attempt native HTML5 + // native: forces HTML5 playback + // shim: disallows HTML5, will attempt either Flash or Silverlight + // none: forces fallback view + mode: 'auto', + // remove or reorder to change plugin priority and availability + plugins: ['flash','silverlight','youtube','vimeo'], + // shows debug errors on screen + enablePluginDebug: false, + // use plugin for browsers that have trouble with Basic Authentication on HTTPS sites + httpsBasicAuthSite: false, + // overrides the type specified, useful for dynamic instantiation + type: '', + // path to Flash and Silverlight plugins + pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']), + // name of flash file + flashName: 'flashmediaelement.swf', + // streamer for RTMP streaming + flashStreamer: '', + // turns on the smoothing filter in Flash + enablePluginSmoothing: false, + // enabled pseudo-streaming (seek) on .mp4 files + enablePseudoStreaming: false, + // start query parameter sent to server for pseudo-streaming + pseudoStreamingStartQueryParam: 'start', + // name of silverlight file + silverlightName: 'silverlightmediaelement.xap', + // default if the <video width> is not specified + defaultVideoWidth: 480, + // default if the <video height> is not specified + defaultVideoHeight: 270, + // overrides <video width> + pluginWidth: -1, + // overrides <video height> + pluginHeight: -1, + // additional plugin variables in 'key=value' form + pluginVars: [], + // rate in milliseconds for Flash and Silverlight to fire the timeupdate event + // larger number is less accurate, but less strain on plugin->JavaScript bridge + timerRate: 250, + // initial volume for player + startVolume: 0.8, + success: function () { }, + error: function () { } +}; + +/* +Determines if a browser supports the <video> or <audio> element +and returns either the native element or a Flash/Silverlight version that +mimics HTML5 MediaElement +*/ +mejs.MediaElement = function (el, o) { + return mejs.HtmlMediaElementShim.create(el,o); +}; + +mejs.HtmlMediaElementShim = { + + create: function(el, o) { + var + options = mejs.MediaElementDefaults, + htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el, + tagName = htmlMediaElement.tagName.toLowerCase(), + isMediaTag = (tagName === 'audio' || tagName === 'video'), + src = (isMediaTag) ? htmlMediaElement.getAttribute('src') : htmlMediaElement.getAttribute('href'), + poster = htmlMediaElement.getAttribute('poster'), + autoplay = htmlMediaElement.getAttribute('autoplay'), + preload = htmlMediaElement.getAttribute('preload'), + controls = htmlMediaElement.getAttribute('controls'), + playback, + prop; + + // extend options + for (prop in o) { + options[prop] = o[prop]; + } + + // clean up attributes + src = (typeof src == 'undefined' || src === null || src == '') ? null : src; + poster = (typeof poster == 'undefined' || poster === null) ? '' : poster; + preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload; + autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false'); + controls = !(typeof controls == 'undefined' || controls === null || controls === 'false'); + + // test for HTML5 and plugin capabilities + playback = this.determinePlayback(htmlMediaElement, options, mejs.MediaFeatures.supportsMediaTag, isMediaTag, src); + playback.url = (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : ''; + + if (playback.method == 'native') { + // second fix for android + if (mejs.MediaFeatures.isBustedAndroid) { + htmlMediaElement.src = playback.url; + htmlMediaElement.addEventListener('click', function() { + htmlMediaElement.play(); + }, false); + } + + // add methods to native HTMLMediaElement + return this.updateNative(playback, options, autoplay, preload); + } else if (playback.method !== '') { + // create plugin to mimic HTMLMediaElement + + return this.createPlugin( playback, options, poster, autoplay, preload, controls); + } else { + // boo, no HTML5, no Flash, no Silverlight. + this.createErrorMessage( playback, options, poster ); + + return this; + } + }, + + determinePlayback: function(htmlMediaElement, options, supportsMediaTag, isMediaTag, src) { + var + mediaFiles = [], + i, + j, + k, + l, + n, + type, + result = { method: '', url: '', htmlMediaElement: htmlMediaElement, isVideo: (htmlMediaElement.tagName.toLowerCase() != 'audio')}, + pluginName, + pluginVersions, + pluginInfo, + dummy, + media; + + // STEP 1: Get URL and type from <video src> or <source src> + + // supplied type overrides <video type> and <source type> + if (typeof options.type != 'undefined' && options.type !== '') { + + // accept either string or array of types + if (typeof options.type == 'string') { + mediaFiles.push({type:options.type, url:src}); + } else { + + for (i=0; i<options.type.length; i++) { + mediaFiles.push({type:options.type[i], url:src}); + } + } + + // test for src attribute first + } else if (src !== null) { + type = this.formatType(src, htmlMediaElement.getAttribute('type')); + mediaFiles.push({type:type, url:src}); + + // then test for <source> elements + } else { + // test <source> types to see if they are usable + for (i = 0; i < htmlMediaElement.childNodes.length; i++) { + n = htmlMediaElement.childNodes[i]; + if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') { + src = n.getAttribute('src'); + type = this.formatType(src, n.getAttribute('type')); + media = n.getAttribute('media'); + + if (!media || !window.matchMedia || (window.matchMedia && window.matchMedia(media).matches)) { + mediaFiles.push({type:type, url:src}); + } + } + } + } + + // in the case of dynamicly created players + // check for audio types + if (!isMediaTag && mediaFiles.length > 0 && mediaFiles[0].url !== null && this.getTypeFromFile(mediaFiles[0].url).indexOf('audio') > -1) { + result.isVideo = false; + } + + + // STEP 2: Test for playback method + + // special case for Android which sadly doesn't implement the canPlayType function (always returns '') + if (mejs.MediaFeatures.isBustedAndroid) { + htmlMediaElement.canPlayType = function(type) { + return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : ''; + }; + } + + // special case for Chromium to specify natively supported video codecs (i.e. WebM and Theora) + if (mejs.MediaFeatures.isChromium) { + htmlMediaElement.canPlayType = function(type) { + return (type.match(/video\/(webm|ogv|ogg)/gi) !== null) ? 'maybe' : ''; + }; + } + + // test for native playback first + if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'native') && !(mejs.MediaFeatures.isBustedNativeHTTPS && options.httpsBasicAuthSite === true)) { + + if (!isMediaTag) { + + // create a real HTML5 Media Element + dummy = document.createElement( result.isVideo ? 'video' : 'audio'); + htmlMediaElement.parentNode.insertBefore(dummy, htmlMediaElement); + htmlMediaElement.style.display = 'none'; + + // use this one from now on + result.htmlMediaElement = htmlMediaElement = dummy; + } + + for (i=0; i<mediaFiles.length; i++) { + // normal check + if (mediaFiles[i].type == "video/m3u8" || htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '' + // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg') + || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '' + // special case for m4a supported by detecting mp4 support + || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/m4a/,'mp4')).replace(/no/, '') !== '') { + result.method = 'native'; + result.url = mediaFiles[i].url; + break; + } + } + + if (result.method === 'native') { + if (result.url !== null) { + htmlMediaElement.src = result.url; + } + + // if `auto_plugin` mode, then cache the native result but try plugins. + if (options.mode !== 'auto_plugin') { + return result; + } + } + } + + // if native playback didn't work, then test plugins + if (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'shim') { + for (i=0; i<mediaFiles.length; i++) { + type = mediaFiles[i].type; + + // test all plugins in order of preference [silverlight, flash] + for (j=0; j<options.plugins.length; j++) { + + pluginName = options.plugins[j]; + + // test version of plugin (for future features) + pluginVersions = mejs.plugins[pluginName]; + + for (k=0; k<pluginVersions.length; k++) { + pluginInfo = pluginVersions[k]; + + // test if user has the correct plugin version + + // for youtube/vimeo + if (pluginInfo.version == null || + + mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) { + + // test for plugin playback types + for (l=0; l<pluginInfo.types.length; l++) { + // find plugin that can play the type + if (type == pluginInfo.types[l]) { + result.method = pluginName; + result.url = mediaFiles[i].url; + return result; + } + } + } + } + } + } + } + + // at this point, being in 'auto_plugin' mode implies that we tried plugins but failed. + // if we have native support then return that. + if (options.mode === 'auto_plugin' && result.method === 'native') { + return result; + } + + // what if there's nothing to play? just grab the first available + if (result.method === '' && mediaFiles.length > 0) { + result.url = mediaFiles[0].url; + } + + return result; + }, + + formatType: function(url, type) { + var ext; + + // if no type is supplied, fake it with the extension + if (url && !type) { + return this.getTypeFromFile(url); + } else { + // only return the mime part of the type in case the attribute contains the codec + // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element + // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4` + + if (type && ~type.indexOf(';')) { + return type.substr(0, type.indexOf(';')); + } else { + return type; + } + } + }, + + getTypeFromFile: function(url) { + url = url.split('?')[0]; + var ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase(); + return (/(mp4|m4v|ogg|ogv|m3u8|webm|webmv|flv|wmv|mpeg|mov)/gi.test(ext) ? 'video' : 'audio') + '/' + this.getTypeFromExtension(ext); + }, + + getTypeFromExtension: function(ext) { + + switch (ext) { + case 'mp4': + case 'm4v': + case 'm4a': + return 'mp4'; + case 'webm': + case 'webma': + case 'webmv': + return 'webm'; + case 'ogg': + case 'oga': + case 'ogv': + return 'ogg'; + default: + return ext; + } + }, + + createErrorMessage: function(playback, options, poster) { + var + htmlMediaElement = playback.htmlMediaElement, + errorContainer = document.createElement('div'); + + errorContainer.className = 'me-cannotplay'; + + try { + errorContainer.style.width = htmlMediaElement.width + 'px'; + errorContainer.style.height = htmlMediaElement.height + 'px'; + } catch (e) {} + + if (options.customError) { + errorContainer.innerHTML = options.customError; + } else { + errorContainer.innerHTML = (poster !== '') ? + '<a href="' + playback.url + '"><img src="' + poster + '" width="100%" height="100%" /></a>' : + '<a href="' + playback.url + '"><span>' + mejs.i18n.t('Download File') + '</span></a>'; + } + + htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement); + htmlMediaElement.style.display = 'none'; + + options.error(htmlMediaElement); + }, + + createPlugin:function(playback, options, poster, autoplay, preload, controls) { + var + htmlMediaElement = playback.htmlMediaElement, + width = 1, + height = 1, + pluginid = 'me_' + playback.method + '_' + (mejs.meIndex++), + pluginMediaElement = new mejs.PluginMediaElement(pluginid, playback.method, playback.url), + container = document.createElement('div'), + specialIEContainer, + node, + initVars; + + // copy tagName from html media element + pluginMediaElement.tagName = htmlMediaElement.tagName + + // copy attributes from html media element to plugin media element + for (var i = 0; i < htmlMediaElement.attributes.length; i++) { + var attribute = htmlMediaElement.attributes[i]; + if (attribute.specified == true) { + pluginMediaElement.setAttribute(attribute.name, attribute.value); + } + } + + // check for placement inside a <p> tag (sometimes WYSIWYG editors do this) + node = htmlMediaElement.parentNode; + while (node !== null && node.tagName.toLowerCase() !== 'body' && node.parentNode != null) { + if (node.parentNode.tagName.toLowerCase() === 'p') { + node.parentNode.parentNode.insertBefore(node, node.parentNode); + break; + } + node = node.parentNode; + } + + if (playback.isVideo) { + width = (options.pluginWidth > 0) ? options.pluginWidth : (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth; + height = (options.pluginHeight > 0) ? options.pluginHeight : (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight; + + // in case of '%' make sure it's encoded + width = mejs.Utility.encodeUrl(width); + height = mejs.Utility.encodeUrl(height); + + } else { + if (options.enablePluginDebug) { + width = 320; + height = 240; + } + } + + // register plugin + pluginMediaElement.success = options.success; + mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement); + + // add container (must be added to DOM before inserting HTML for IE) + container.className = 'me-plugin'; + container.id = pluginid + '_container'; + + if (playback.isVideo) { + htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement); + } else { + document.body.insertBefore(container, document.body.childNodes[0]); + } + + // flash/silverlight vars + initVars = [ + 'id=' + pluginid, + 'isvideo=' + ((playback.isVideo) ? "true" : "false"), + 'autoplay=' + ((autoplay) ? "true" : "false"), + 'preload=' + preload, + 'width=' + width, + 'startvolume=' + options.startVolume, + 'timerrate=' + options.timerRate, + 'flashstreamer=' + options.flashStreamer, + 'height=' + height, + 'pseudostreamstart=' + options.pseudoStreamingStartQueryParam]; + + if (playback.url !== null) { + if (playback.method == 'flash') { + initVars.push('file=' + mejs.Utility.encodeUrl(playback.url)); + } else { + initVars.push('file=' + playback.url); + } + } + if (options.enablePluginDebug) { + initVars.push('debug=true'); + } + if (options.enablePluginSmoothing) { + initVars.push('smoothing=true'); + } + if (options.enablePseudoStreaming) { + initVars.push('pseudostreaming=true'); + } + if (controls) { + initVars.push('controls=true'); // shows controls in the plugin if desired + } + if (options.pluginVars) { + initVars = initVars.concat(options.pluginVars); + } + + switch (playback.method) { + case 'silverlight': + container.innerHTML = +'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' + +'<param name="initParams" value="' + initVars.join(',') + '" />' + +'<param name="windowless" value="true" />' + +'<param name="background" value="black" />' + +'<param name="minRuntimeVersion" value="3.0.0.0" />' + +'<param name="autoUpgrade" value="true" />' + +'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' + +'</object>'; + break; + + case 'flash': + + if (mejs.MediaFeatures.isIE) { + specialIEContainer = document.createElement('div'); + container.appendChild(specialIEContainer); + specialIEContainer.outerHTML = +'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + +'id="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' + +'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' + +'<param name="flashvars" value="' + initVars.join('&') + '" />' + +'<param name="quality" value="high" />' + +'<param name="bgcolor" value="#000000" />' + +'<param name="wmode" value="transparent" />' + +'<param name="allowScriptAccess" value="always" />' + +'<param name="allowFullScreen" value="true" />' + +'<param name="scale" value="default" />' + +'</object>'; + + } else { + + container.innerHTML = +'<embed id="' + pluginid + '" name="' + pluginid + '" ' + +'play="true" ' + +'loop="false" ' + +'quality="high" ' + +'bgcolor="#000000" ' + +'wmode="transparent" ' + +'allowScriptAccess="always" ' + +'allowFullScreen="true" ' + +'type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" ' + +'src="' + options.pluginPath + options.flashName + '" ' + +'flashvars="' + initVars.join('&') + '" ' + +'width="' + width + '" ' + +'height="' + height + '" ' + +'scale="default"' + +'class="mejs-shim"></embed>'; + } + break; + + case 'youtube': + + + var videoId; + // youtu.be url from share button + if (playback.url.lastIndexOf("youtu.be") != -1) { + videoId = playback.url.substr(playback.url.lastIndexOf('/')+1); + if (videoId.indexOf('?') != -1) { + videoId = videoId.substr(0, videoId.indexOf('?')); + } + } + else { + videoId = playback.url.substr(playback.url.lastIndexOf('=')+1); + } + youtubeSettings = { + container: container, + containerId: container.id, + pluginMediaElement: pluginMediaElement, + pluginId: pluginid, + videoId: videoId, + height: height, + width: width + }; + + if (mejs.PluginDetector.hasPluginVersion('flash', [10,0,0]) ) { + mejs.YouTubeApi.createFlash(youtubeSettings); + } else { + mejs.YouTubeApi.enqueueIframe(youtubeSettings); + } + + break; + + // DEMO Code. Does NOT work. + case 'vimeo': + var player_id = pluginid + "_player"; + pluginMediaElement.vimeoid = playback.url.substr(playback.url.lastIndexOf('/')+1); + + container.innerHTML ='<iframe src="//player.vimeo.com/video/' + pluginMediaElement.vimeoid + '?api=1&portrait=0&byline=0&title=0&player_id=' + player_id + '" width="' + width +'" height="' + height +'" frameborder="0" class="mejs-shim" id="' + player_id + '"></iframe>'; + if (typeof($f) == 'function') { // froogaloop available + var player = $f(container.childNodes[0]); + player.addEvent('ready', function() { + $.extend( player, { + playVideo: function() { + player.api( 'play' ); + }, + stopVideo: function() { + player.api( 'unload' ); + }, + pauseVideo: function() { + player.api( 'pause' ); + }, + seekTo: function( seconds ) { + player.api( 'seekTo', seconds ); + }, + setVolume: function( volume ) { + player.api( 'setVolume', volume ); + }, + setMuted: function( muted ) { + if( muted ) { + player.lastVolume = player.api( 'getVolume' ); + player.api( 'setVolume', 0 ); + } else { + player.api( 'setVolume', player.lastVolume ); + delete player.lastVolume; + } + } + }); + + function createEvent(player, pluginMediaElement, eventName, e) { + var obj = { + type: eventName, + target: pluginMediaElement + }; + if (eventName == 'timeupdate') { + pluginMediaElement.currentTime = obj.currentTime = e.seconds; + pluginMediaElement.duration = obj.duration = e.duration; + } + pluginMediaElement.dispatchEvent(obj.type, obj); + } + + player.addEvent('play', function() { + createEvent(player, pluginMediaElement, 'play'); + createEvent(player, pluginMediaElement, 'playing'); + }); + + player.addEvent('pause', function() { + createEvent(player, pluginMediaElement, 'pause'); + }); + + player.addEvent('finish', function() { + createEvent(player, pluginMediaElement, 'ended'); + }); + + player.addEvent('playProgress', function(e) { + createEvent(player, pluginMediaElement, 'timeupdate', e); + }); + + pluginMediaElement.pluginElement = container; + pluginMediaElement.pluginApi = player; + + // init mejs + mejs.MediaPluginBridge.initPlugin(pluginid); + }); + } + else { + console.warn("You need to include froogaloop for vimeo to work"); + } + break; + } + // hide original element + htmlMediaElement.style.display = 'none'; + // prevent browser from autoplaying when using a plugin + htmlMediaElement.removeAttribute('autoplay'); + + // FYI: options.success will be fired by the MediaPluginBridge + + return pluginMediaElement; + }, + + updateNative: function(playback, options, autoplay, preload) { + + var htmlMediaElement = playback.htmlMediaElement, + m; + + + // add methods to video object to bring it into parity with Flash Object + for (m in mejs.HtmlMediaElement) { + htmlMediaElement[m] = mejs.HtmlMediaElement[m]; + } + + /* + Chrome now supports preload="none" + if (mejs.MediaFeatures.isChrome) { + + // special case to enforce preload attribute (Chrome doesn't respect this) + if (preload === 'none' && !autoplay) { + + // forces the browser to stop loading (note: fails in IE9) + htmlMediaElement.src = ''; + htmlMediaElement.load(); + htmlMediaElement.canceledPreload = true; + + htmlMediaElement.addEventListener('play',function() { + if (htmlMediaElement.canceledPreload) { + htmlMediaElement.src = playback.url; + htmlMediaElement.load(); + htmlMediaElement.play(); + htmlMediaElement.canceledPreload = false; + } + }, false); + // for some reason Chrome forgets how to autoplay sometimes. + } else if (autoplay) { + htmlMediaElement.load(); + htmlMediaElement.play(); + } + } + */ + + // fire success code + options.success(htmlMediaElement, htmlMediaElement); + + return htmlMediaElement; + } +}; + +/* + - test on IE (object vs. embed) + - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE) + - fullscreen? +*/ + +// YouTube Flash and Iframe API +mejs.YouTubeApi = { + isIframeStarted: false, + isIframeLoaded: false, + loadIframeApi: function() { + if (!this.isIframeStarted) { + var tag = document.createElement('script'); + tag.src = "//www.youtube.com/player_api"; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + this.isIframeStarted = true; + } + }, + iframeQueue: [], + enqueueIframe: function(yt) { + + if (this.isLoaded) { + this.createIframe(yt); + } else { + this.loadIframeApi(); + this.iframeQueue.push(yt); + } + }, + createIframe: function(settings) { + + var + pluginMediaElement = settings.pluginMediaElement, + player = new YT.Player(settings.containerId, { + height: settings.height, + width: settings.width, + videoId: settings.videoId, + playerVars: {controls:0}, + events: { + 'onReady': function() { + + // hook up iframe object to MEjs + settings.pluginMediaElement.pluginApi = player; + + // init mejs + mejs.MediaPluginBridge.initPlugin(settings.pluginId); + + // create timer + setInterval(function() { + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); + }, 250); + }, + 'onStateChange': function(e) { + + mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement); + + } + } + }); + }, + + createEvent: function (player, pluginMediaElement, eventName) { + var obj = { + type: eventName, + target: pluginMediaElement + }; + + if (player && player.getDuration) { + + // time + pluginMediaElement.currentTime = obj.currentTime = player.getCurrentTime(); + pluginMediaElement.duration = obj.duration = player.getDuration(); + + // state + obj.paused = pluginMediaElement.paused; + obj.ended = pluginMediaElement.ended; + + // sound + obj.muted = player.isMuted(); + obj.volume = player.getVolume() / 100; + + // progress + obj.bytesTotal = player.getVideoBytesTotal(); + obj.bufferedBytes = player.getVideoBytesLoaded(); + + // fake the W3C buffered TimeRange + var bufferedTime = obj.bufferedBytes / obj.bytesTotal * obj.duration; + + obj.target.buffered = obj.buffered = { + start: function(index) { + return 0; + }, + end: function (index) { + return bufferedTime; + }, + length: 1 + }; + + } + + // send event up the chain + pluginMediaElement.dispatchEvent(obj.type, obj); + }, + + iFrameReady: function() { + + this.isLoaded = true; + this.isIframeLoaded = true; + + while (this.iframeQueue.length > 0) { + var settings = this.iframeQueue.pop(); + this.createIframe(settings); + } + }, + + // FLASH! + flashPlayers: {}, + createFlash: function(settings) { + + this.flashPlayers[settings.pluginId] = settings; + + /* + settings.container.innerHTML = + '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0" ' + + 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' + + '<param name="allowScriptAccess" value="always">' + + '<param name="wmode" value="transparent">' + + '</object>'; + */ + + var specialIEContainer, + youtubeUrl = '//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0'; + + if (mejs.MediaFeatures.isIE) { + + specialIEContainer = document.createElement('div'); + settings.container.appendChild(specialIEContainer); + specialIEContainer.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + +'id="' + settings.pluginId + '" width="' + settings.width + '" height="' + settings.height + '" class="mejs-shim">' + + '<param name="movie" value="' + youtubeUrl + '" />' + + '<param name="wmode" value="transparent" />' + + '<param name="allowScriptAccess" value="always" />' + + '<param name="allowFullScreen" value="true" />' + +'</object>'; + } else { + settings.container.innerHTML = + '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="' + youtubeUrl + '" ' + + 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' + + '<param name="allowScriptAccess" value="always">' + + '<param name="wmode" value="transparent">' + + '</object>'; + } + + }, + + flashReady: function(id) { + var + settings = this.flashPlayers[id], + player = document.getElementById(id), + pluginMediaElement = settings.pluginMediaElement; + + // hook up and return to MediaELementPlayer.success + pluginMediaElement.pluginApi = + pluginMediaElement.pluginElement = player; + mejs.MediaPluginBridge.initPlugin(id); + + // load the youtube video + player.cueVideoById(settings.videoId); + + var callbackName = settings.containerId + '_callback'; + + window[callbackName] = function(e) { + mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement); + } + + player.addEventListener('onStateChange', callbackName); + + setInterval(function() { + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); + }, 250); + + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'canplay'); + }, + + handleStateChange: function(youTubeState, player, pluginMediaElement) { + switch (youTubeState) { + case -1: // not started + pluginMediaElement.paused = true; + pluginMediaElement.ended = true; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata'); + //createYouTubeEvent(player, pluginMediaElement, 'loadeddata'); + break; + case 0: + pluginMediaElement.paused = false; + pluginMediaElement.ended = true; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended'); + break; + case 1: + pluginMediaElement.paused = false; + pluginMediaElement.ended = false; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play'); + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing'); + break; + case 2: + pluginMediaElement.paused = true; + pluginMediaElement.ended = false; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause'); + break; + case 3: // buffering + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress'); + break; + case 5: + // cued? + break; + + } + + } +} +// IFRAME +function onYouTubePlayerAPIReady() { + mejs.YouTubeApi.iFrameReady(); +} +// FLASH +function onYouTubePlayerReady(id) { + mejs.YouTubeApi.flashReady(id); +} + +window.mejs = mejs; +window.MediaElement = mejs.MediaElement; + +/*! + * Adds Internationalization and localization to mediaelement. + * + * This file does not contain translations, you have to add the manually. + * The schema is always the same: me-i18n-locale-[ISO_639-1 Code].js + * + * Examples are provided both for german and chinese translation. + * + * + * What is the concept beyond i18n? + * http://en.wikipedia.org/wiki/Internationalization_and_localization + * + * What langcode should i use? + * http://en.wikipedia.org/wiki/ISO_639-1 + * + * + * License? + * + * The i18n file uses methods from the Drupal project (drupal.js): + * - i18n.methods.t() (modified) + * - i18n.methods.checkPlain() (full copy) + * + * The Drupal project is (like mediaelementjs) licensed under GPLv2. + * - http://drupal.org/licensing/faq/#q1 + * - https://github.com/johndyer/mediaelement + * - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * + * @params + * - context - document, iframe .. + * - exports - CommonJS, window .. + * + */ +;(function(context, exports, undefined) { + "use strict"; + var i18n = { + "locale": { + "language" : '', + "strings" : {} + }, + "methods" : {} + }; +// start i18n + + + /** + * Get language, fallback to browser's language if empty + */ + i18n.getLanguage = function () { + var language = i18n.locale.language || window.navigator.userLanguage || window.navigator.language; + // convert to iso 639-1 (2-letters, lower case) + return language.substr(0, 2).toLowerCase(); + }; + + // i18n fixes for compatibility with WordPress + if ( typeof mejsL10n != 'undefined' ) { + i18n.locale.language = mejsL10n.language; + } + + + + /** + * Encode special characters in a plain-text string for display as HTML. + */ + i18n.methods.checkPlain = function (str) { + var character, regex, + replace = { + '&': '&', + '"': '"', + '<': '<', + '>': '>' + }; + str = String(str); + for (character in replace) { + if (replace.hasOwnProperty(character)) { + regex = new RegExp(character, 'g'); + str = str.replace(regex, replace[character]); + } + } + return str; + }; + + /** + * Translate strings to the page language or a given language. + * + * + * @param str + * A string containing the English string to translate. + * + * @param options + * - 'context' (defaults to the default context): The context the source string + * belongs to. + * + * @return + * The translated string, escaped via i18n.methods.checkPlain() + */ + i18n.methods.t = function (str, options) { + + // Fetch the localized version of the string. + if (i18n.locale.strings && i18n.locale.strings[options.context] && i18n.locale.strings[options.context][str]) { + str = i18n.locale.strings[options.context][str]; + } + + return i18n.methods.checkPlain(str); + }; + + + /** + * Wrapper for i18n.methods.t() + * + * @see i18n.methods.t() + * @throws InvalidArgumentException + */ + i18n.t = function(str, options) { + + if (typeof str === 'string' && str.length > 0) { + + // check every time due language can change for + // different reasons (translation, lang switcher ..) + var language = i18n.getLanguage(); + + options = options || { + "context" : language + }; + + return i18n.methods.t(str, options); + } + else { + throw { + "name" : 'InvalidArgumentException', + "message" : 'First argument is either not a string or empty.' + }; + } + }; + +// end i18n + exports.i18n = i18n; +}(document, mejs)); + +// i18n fixes for compatibility with WordPress +;(function(exports, undefined) { + + "use strict"; + + if ( typeof mejsL10n != 'undefined' ) { + exports[mejsL10n.language] = mejsL10n.strings; + } + +}(mejs.i18n.locale.strings)); + +/*! + * This is a i18n.locale language object. + * + * German translation by Tim Latz, latz.tim@gmail.com + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + if (typeof exports.de === 'undefined') { + exports.de = { + "Fullscreen" : "Vollbild", + "Go Fullscreen" : "Vollbild an", + "Turn off Fullscreen" : "Vollbild aus", + "Close" : "Schließen" + }; + } + +}(mejs.i18n.locale.strings)); +/*! + * This is a i18n.locale language object. + * + * Traditional chinese translation by Tim Latz, latz.tim@gmail.com + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + if (typeof exports.zh === 'undefined') { + exports.zh = { + "Fullscreen" : "全螢幕", + "Go Fullscreen" : "全屏模式", + "Turn off Fullscreen" : "退出全屏模式", + "Close" : "關閉" + }; + } + +}(mejs.i18n.locale.strings)); + diff --git a/js/mediaelement/build/mediaelement.min.js b/js/mediaelement/build/mediaelement.min.js new file mode 100644 index 0000000000000000000000000000000000000000..d14d65f40d96eb2201453f9686ad47fa1c0b2e05 --- /dev/null +++ b/js/mediaelement/build/mediaelement.min.js @@ -0,0 +1,72 @@ +/*! +* MediaElement.js +* HTML5 <video> and <audio> shim and player +* http://mediaelementjs.com/ +* +* Creates a JavaScript object that mimics HTML5 MediaElement API +* for browsers that don't understand HTML5 or can't play the provided codec +* Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3 +* +* Copyright 2010-2014, John Dyer (http://j.hn) +* License: MIT +* +*/var mejs=mejs||{};mejs.version="2.15.1";mejs.meIndex=0; +mejs.plugins={silverlight:[{version:[3,0],types:["video/mp4","video/m4v","video/mov","video/wmv","audio/wma","audio/m4a","audio/mp3","audio/wav","audio/mpeg"]}],flash:[{version:[9,0,124],types:["video/mp4","video/m4v","video/mov","video/flv","video/rtmp","video/x-flv","audio/flv","audio/x-flv","audio/mp3","audio/m4a","audio/mpeg","video/youtube","video/x-youtube","application/x-mpegURL"]}],youtube:[{version:null,types:["video/youtube","video/x-youtube","audio/youtube","audio/x-youtube"]}],vimeo:[{version:null, +types:["video/vimeo","video/x-vimeo"]}]}; +mejs.Utility={encodeUrl:function(a){return encodeURIComponent(a)},escapeHTML:function(a){return a.toString().split("&").join("&").split("<").join("<").split('"').join(""")},absolutizeUrl:function(a){var b=document.createElement("div");b.innerHTML='<a href="'+this.escapeHTML(a)+'">x</a>';return b.firstChild.href},getScriptPath:function(a){for(var b=0,c,d="",e="",g,f,i=document.getElementsByTagName("script"),k=i.length,h=a.length;b<k;b++){g=i[b].src;c=g.lastIndexOf("/");if(c>-1){f=g.substring(c+ +1);g=g.substring(0,c+1)}else{f=g;g=""}for(c=0;c<h;c++){e=a[c];e=f.indexOf(e);if(e>-1){d=g;break}}if(d!=="")break}return d},secondsToTimeCode:function(a,b,c,d){if(typeof c=="undefined")c=false;else if(typeof d=="undefined")d=25;var e=Math.floor(a/3600)%24,g=Math.floor(a/60)%60,f=Math.floor(a%60);a=Math.floor((a%1*d).toFixed(3));return(b||e>0?(e<10?"0"+e:e)+":":"")+(g<10?"0"+g:g)+":"+(f<10?"0"+f:f)+(c?":"+(a<10?"0"+a:a):"")},timeCodeToSeconds:function(a,b,c,d){if(typeof c=="undefined")c=false;else if(typeof d== +"undefined")d=25;a=a.split(":");b=parseInt(a[0],10);var e=parseInt(a[1],10),g=parseInt(a[2],10),f=0,i=0;if(c)f=parseInt(a[3])/d;return i=b*3600+e*60+g+f},convertSMPTEtoSeconds:function(a){if(typeof a!="string")return false;a=a.replace(",",".");var b=0,c=a.indexOf(".")!=-1?a.split(".")[1].length:0,d=1;a=a.split(":").reverse();for(var e=0;e<a.length;e++){d=1;if(e>0)d=Math.pow(60,e);b+=Number(a[e])*d}return Number(b.toFixed(c))},removeSwf:function(a){var b=document.getElementById(a);if(b&&/object|embed/i.test(b.nodeName))if(mejs.MediaFeatures.isIE){b.style.display= +"none";(function(){b.readyState==4?mejs.Utility.removeObjectInIE(a):setTimeout(arguments.callee,10)})()}else b.parentNode.removeChild(b)},removeObjectInIE:function(a){if(a=document.getElementById(a)){for(var b in a)if(typeof a[b]=="function")a[b]=null;a.parentNode.removeChild(a)}}}; +mejs.PluginDetector={hasPluginVersion:function(a,b){var c=this.plugins[a];b[1]=b[1]||0;b[2]=b[2]||0;return c[0]>b[0]||c[0]==b[0]&&c[1]>b[1]||c[0]==b[0]&&c[1]==b[1]&&c[2]>=b[2]?true:false},nav:window.navigator,ua:window.navigator.userAgent.toLowerCase(),plugins:[],addPlugin:function(a,b,c,d,e){this.plugins[a]=this.detectPlugin(b,c,d,e)},detectPlugin:function(a,b,c,d){var e=[0,0,0],g;if(typeof this.nav.plugins!="undefined"&&typeof this.nav.plugins[a]=="object"){if((c=this.nav.plugins[a].description)&& +!(typeof this.nav.mimeTypes!="undefined"&&this.nav.mimeTypes[b]&&!this.nav.mimeTypes[b].enabledPlugin)){e=c.replace(a,"").replace(/^\s+/,"").replace(/\sr/gi,".").split(".");for(a=0;a<e.length;a++)e[a]=parseInt(e[a].match(/\d+/),10)}}else if(typeof window.ActiveXObject!="undefined")try{if(g=new ActiveXObject(c))e=d(g)}catch(f){}return e}}; +mejs.PluginDetector.addPlugin("flash","Shockwave Flash","application/x-shockwave-flash","ShockwaveFlash.ShockwaveFlash",function(a){var b=[];if(a=a.GetVariable("$version")){a=a.split(" ")[1].split(",");b=[parseInt(a[0],10),parseInt(a[1],10),parseInt(a[2],10)]}return b}); +mejs.PluginDetector.addPlugin("silverlight","Silverlight Plug-In","application/x-silverlight-2","AgControl.AgControl",function(a){var b=[0,0,0,0],c=function(d,e,g,f){for(;d.isVersionSupported(e[0]+"."+e[1]+"."+e[2]+"."+e[3]);)e[g]+=f;e[g]-=f};c(a,b,0,1);c(a,b,1,1);c(a,b,2,1E4);c(a,b,2,1E3);c(a,b,2,100);c(a,b,2,10);c(a,b,2,1);c(a,b,3,1);return b}); +mejs.MediaFeatures={init:function(){var a=this,b=document,c=mejs.PluginDetector.nav,d=mejs.PluginDetector.ua.toLowerCase(),e,g=["source","track","audio","video"];a.isiPad=d.match(/ipad/i)!==null;a.isiPhone=d.match(/iphone/i)!==null;a.isiOS=a.isiPhone||a.isiPad;a.isAndroid=d.match(/android/i)!==null;a.isBustedAndroid=d.match(/android 2\.[12]/)!==null;a.isBustedNativeHTTPS=location.protocol==="https:"&&(d.match(/android [12]\./)!==null||d.match(/macintosh.* version.* safari/)!==null);a.isIE=c.appName.toLowerCase().indexOf("microsoft")!= +-1||c.appName.toLowerCase().match(/trident/gi)!==null;a.isChrome=d.match(/chrome/gi)!==null;a.isChromium=d.match(/chromium/gi)!==null;a.isFirefox=d.match(/firefox/gi)!==null;a.isWebkit=d.match(/webkit/gi)!==null;a.isGecko=d.match(/gecko/gi)!==null&&!a.isWebkit&&!a.isIE;a.isOpera=d.match(/opera/gi)!==null;a.hasTouch="ontouchstart"in window;a.svg=!!document.createElementNS&&!!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect;for(c=0;c<g.length;c++)e=document.createElement(g[c]); +a.supportsMediaTag=typeof e.canPlayType!=="undefined"||a.isBustedAndroid;try{e.canPlayType("video/mp4")}catch(f){a.supportsMediaTag=false}a.hasSemiNativeFullScreen=typeof e.webkitEnterFullscreen!=="undefined";a.hasNativeFullscreen=typeof e.requestFullscreen!=="undefined";a.hasWebkitNativeFullScreen=typeof e.webkitRequestFullScreen!=="undefined";a.hasMozNativeFullScreen=typeof e.mozRequestFullScreen!=="undefined";a.hasMsNativeFullScreen=typeof e.msRequestFullscreen!=="undefined";a.hasTrueNativeFullScreen= +a.hasWebkitNativeFullScreen||a.hasMozNativeFullScreen||a.hasMsNativeFullScreen;a.nativeFullScreenEnabled=a.hasTrueNativeFullScreen;if(a.hasMozNativeFullScreen)a.nativeFullScreenEnabled=document.mozFullScreenEnabled;else if(a.hasMsNativeFullScreen)a.nativeFullScreenEnabled=document.msFullscreenEnabled;if(a.isChrome)a.hasSemiNativeFullScreen=false;if(a.hasTrueNativeFullScreen){a.fullScreenEventName="";if(a.hasWebkitNativeFullScreen)a.fullScreenEventName="webkitfullscreenchange";else if(a.hasMozNativeFullScreen)a.fullScreenEventName= +"mozfullscreenchange";else if(a.hasMsNativeFullScreen)a.fullScreenEventName="MSFullscreenChange";a.isFullScreen=function(){if(a.hasMozNativeFullScreen)return b.mozFullScreen;else if(a.hasWebkitNativeFullScreen)return b.webkitIsFullScreen;else if(a.hasMsNativeFullScreen)return b.msFullscreenElement!==null};a.requestFullScreen=function(i){if(a.hasWebkitNativeFullScreen)i.webkitRequestFullScreen();else if(a.hasMozNativeFullScreen)i.mozRequestFullScreen();else a.hasMsNativeFullScreen&&i.msRequestFullscreen()}; +a.cancelFullScreen=function(){if(a.hasWebkitNativeFullScreen)document.webkitCancelFullScreen();else if(a.hasMozNativeFullScreen)document.mozCancelFullScreen();else a.hasMsNativeFullScreen&&document.msExitFullscreen()}}if(a.hasSemiNativeFullScreen&&d.match(/mac os x 10_5/i)){a.hasNativeFullScreen=false;a.hasSemiNativeFullScreen=false}}};mejs.MediaFeatures.init(); +mejs.HtmlMediaElement={pluginType:"native",isFullScreen:false,setCurrentTime:function(a){this.currentTime=a},setMuted:function(a){this.muted=a},setVolume:function(a){this.volume=a},stop:function(){this.pause()},setSrc:function(a){for(var b=this.getElementsByTagName("source");b.length>0;)this.removeChild(b[0]);if(typeof a=="string")this.src=a;else{var c;for(b=0;b<a.length;b++){c=a[b];if(this.canPlayType(c.type)){this.src=c.src;break}}}},setVideoSize:function(a,b){this.width=a;this.height=b}}; +mejs.PluginMediaElement=function(a,b,c){this.id=a;this.pluginType=b;this.src=c;this.events={};this.attributes={}}; +mejs.PluginMediaElement.prototype={pluginElement:null,pluginType:"",isFullScreen:false,playbackRate:-1,defaultPlaybackRate:-1,seekable:[],played:[],paused:true,ended:false,seeking:false,duration:0,error:null,tagName:"",muted:false,volume:1,currentTime:0,play:function(){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType=="vimeo"?this.pluginApi.playVideo():this.pluginApi.playMedia();this.paused=false}},load:function(){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType== +"vimeo"||this.pluginApi.loadMedia();this.paused=false}},pause:function(){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType=="vimeo"?this.pluginApi.pauseVideo():this.pluginApi.pauseMedia();this.paused=true}},stop:function(){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType=="vimeo"?this.pluginApi.stopVideo():this.pluginApi.stopMedia();this.paused=true}},canPlayType:function(a){var b,c,d,e=mejs.plugins[this.pluginType];for(b=0;b<e.length;b++){d=e[b];if(mejs.PluginDetector.hasPluginVersion(this.pluginType, +d.version))for(c=0;c<d.types.length;c++)if(a==d.types[c])return"probably"}return""},positionFullscreenButton:function(a,b,c){this.pluginApi!=null&&this.pluginApi.positionFullscreenButton&&this.pluginApi.positionFullscreenButton(Math.floor(a),Math.floor(b),c)},hideFullscreenButton:function(){this.pluginApi!=null&&this.pluginApi.hideFullscreenButton&&this.pluginApi.hideFullscreenButton()},setSrc:function(a){if(typeof a=="string"){this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(a));this.src=mejs.Utility.absolutizeUrl(a)}else{var b, +c;for(b=0;b<a.length;b++){c=a[b];if(this.canPlayType(c.type)){this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(c.src));this.src=mejs.Utility.absolutizeUrl(a);break}}}},setCurrentTime:function(a){if(this.pluginApi!=null){this.pluginType=="youtube"||this.pluginType=="vimeo"?this.pluginApi.seekTo(a):this.pluginApi.setCurrentTime(a);this.currentTime=a}},setVolume:function(a){if(this.pluginApi!=null){this.pluginType=="youtube"?this.pluginApi.setVolume(a*100):this.pluginApi.setVolume(a);this.volume=a}}, +setMuted:function(a){if(this.pluginApi!=null){if(this.pluginType=="youtube"){a?this.pluginApi.mute():this.pluginApi.unMute();this.muted=a;this.dispatchEvent("volumechange")}else this.pluginApi.setMuted(a);this.muted=a}},setVideoSize:function(a,b){if(this.pluginElement&&this.pluginElement.style){this.pluginElement.style.width=a+"px";this.pluginElement.style.height=b+"px"}this.pluginApi!=null&&this.pluginApi.setVideoSize&&this.pluginApi.setVideoSize(a,b)},setFullscreen:function(a){this.pluginApi!=null&& +this.pluginApi.setFullscreen&&this.pluginApi.setFullscreen(a)},enterFullScreen:function(){this.pluginApi!=null&&this.pluginApi.setFullscreen&&this.setFullscreen(true)},exitFullScreen:function(){this.pluginApi!=null&&this.pluginApi.setFullscreen&&this.setFullscreen(false)},addEventListener:function(a,b){this.events[a]=this.events[a]||[];this.events[a].push(b)},removeEventListener:function(a,b){if(!a){this.events={};return true}var c=this.events[a];if(!c)return true;if(!b){this.events[a]=[];return true}for(var d= +0;d<c.length;d++)if(c[d]===b){this.events[a].splice(d,1);return true}return false},dispatchEvent:function(a){var b,c,d=this.events[a];if(d){c=Array.prototype.slice.call(arguments,1);for(b=0;b<d.length;b++)d[b].apply(null,c)}},hasAttribute:function(a){return a in this.attributes},removeAttribute:function(a){delete this.attributes[a]},getAttribute:function(a){if(this.hasAttribute(a))return this.attributes[a];return""},setAttribute:function(a,b){this.attributes[a]=b},remove:function(){mejs.Utility.removeSwf(this.pluginElement.id); +mejs.MediaPluginBridge.unregisterPluginElement(this.pluginElement.id)}}; +mejs.MediaPluginBridge={pluginMediaElements:{},htmlMediaElements:{},registerPluginElement:function(a,b,c){this.pluginMediaElements[a]=b;this.htmlMediaElements[a]=c},unregisterPluginElement:function(a){delete this.pluginMediaElements[a];delete this.htmlMediaElements[a]},initPlugin:function(a){var b=this.pluginMediaElements[a],c=this.htmlMediaElements[a];if(b){switch(b.pluginType){case "flash":b.pluginElement=b.pluginApi=document.getElementById(a);break;case "silverlight":b.pluginElement=document.getElementById(b.id); +b.pluginApi=b.pluginElement.Content.MediaElementJS}b.pluginApi!=null&&b.success&&b.success(b,c)}},fireEvent:function(a,b,c){var d,e;if(a=this.pluginMediaElements[a]){b={type:b,target:a};for(d in c){a[d]=c[d];b[d]=c[d]}e=c.bufferedTime||0;b.target.buffered=b.buffered={start:function(){return 0},end:function(){return e},length:1};a.dispatchEvent(b.type,b)}}}; +mejs.MediaElementDefaults={mode:"auto",plugins:["flash","silverlight","youtube","vimeo"],enablePluginDebug:false,httpsBasicAuthSite:false,type:"",pluginPath:mejs.Utility.getScriptPath(["mediaelement.js","mediaelement.min.js","mediaelement-and-player.js","mediaelement-and-player.min.js"]),flashName:"flashmediaelement.swf",flashStreamer:"",enablePluginSmoothing:false,enablePseudoStreaming:false,pseudoStreamingStartQueryParam:"start",silverlightName:"silverlightmediaelement.xap",defaultVideoWidth:480, +defaultVideoHeight:270,pluginWidth:-1,pluginHeight:-1,pluginVars:[],timerRate:250,startVolume:0.8,success:function(){},error:function(){}};mejs.MediaElement=function(a,b){return mejs.HtmlMediaElementShim.create(a,b)}; +mejs.HtmlMediaElementShim={create:function(a,b){var c=mejs.MediaElementDefaults,d=typeof a=="string"?document.getElementById(a):a,e=d.tagName.toLowerCase(),g=e==="audio"||e==="video",f=g?d.getAttribute("src"):d.getAttribute("href");e=d.getAttribute("poster");var i=d.getAttribute("autoplay"),k=d.getAttribute("preload"),h=d.getAttribute("controls"),j;for(j in b)c[j]=b[j];f=typeof f=="undefined"||f===null||f==""?null:f;e=typeof e=="undefined"||e===null?"":e;k=typeof k=="undefined"||k===null||k==="false"? +"none":k;i=!(typeof i=="undefined"||i===null||i==="false");h=!(typeof h=="undefined"||h===null||h==="false");j=this.determinePlayback(d,c,mejs.MediaFeatures.supportsMediaTag,g,f);j.url=j.url!==null?mejs.Utility.absolutizeUrl(j.url):"";if(j.method=="native"){if(mejs.MediaFeatures.isBustedAndroid){d.src=j.url;d.addEventListener("click",function(){d.play()},false)}return this.updateNative(j,c,i,k)}else if(j.method!=="")return this.createPlugin(j,c,e,i,k,h);else{this.createErrorMessage(j,c,e);return this}}, +determinePlayback:function(a,b,c,d,e){var g=[],f,i,k,h={method:"",url:"",htmlMediaElement:a,isVideo:a.tagName.toLowerCase()!="audio"},j;if(typeof b.type!="undefined"&&b.type!=="")if(typeof b.type=="string")g.push({type:b.type,url:e});else for(f=0;f<b.type.length;f++)g.push({type:b.type[f],url:e});else if(e!==null){k=this.formatType(e,a.getAttribute("type"));g.push({type:k,url:e})}else for(f=0;f<a.childNodes.length;f++){i=a.childNodes[f];if(i.nodeType==1&&i.tagName.toLowerCase()=="source"){e=i.getAttribute("src"); +k=this.formatType(e,i.getAttribute("type"));i=i.getAttribute("media");if(!i||!window.matchMedia||window.matchMedia&&window.matchMedia(i).matches)g.push({type:k,url:e})}}if(!d&&g.length>0&&g[0].url!==null&&this.getTypeFromFile(g[0].url).indexOf("audio")>-1)h.isVideo=false;if(mejs.MediaFeatures.isBustedAndroid)a.canPlayType=function(m){return m.match(/video\/(mp4|m4v)/gi)!==null?"maybe":""};if(mejs.MediaFeatures.isChromium)a.canPlayType=function(m){return m.match(/video\/(webm|ogv|ogg)/gi)!==null?"maybe": +""};if(c&&(b.mode==="auto"||b.mode==="auto_plugin"||b.mode==="native")&&!(mejs.MediaFeatures.isBustedNativeHTTPS&&b.httpsBasicAuthSite===true)){if(!d){f=document.createElement(h.isVideo?"video":"audio");a.parentNode.insertBefore(f,a);a.style.display="none";h.htmlMediaElement=a=f}for(f=0;f<g.length;f++)if(g[f].type=="video/m3u8"||a.canPlayType(g[f].type).replace(/no/,"")!==""||a.canPlayType(g[f].type.replace(/mp3/,"mpeg")).replace(/no/,"")!==""||a.canPlayType(g[f].type.replace(/m4a/,"mp4")).replace(/no/, +"")!==""){h.method="native";h.url=g[f].url;break}if(h.method==="native"){if(h.url!==null)a.src=h.url;if(b.mode!=="auto_plugin")return h}}if(b.mode==="auto"||b.mode==="auto_plugin"||b.mode==="shim")for(f=0;f<g.length;f++){k=g[f].type;for(a=0;a<b.plugins.length;a++){e=b.plugins[a];i=mejs.plugins[e];for(c=0;c<i.length;c++){j=i[c];if(j.version==null||mejs.PluginDetector.hasPluginVersion(e,j.version))for(d=0;d<j.types.length;d++)if(k==j.types[d]){h.method=e;h.url=g[f].url;return h}}}}if(b.mode==="auto_plugin"&& +h.method==="native")return h;if(h.method===""&&g.length>0)h.url=g[0].url;return h},formatType:function(a,b){return a&&!b?this.getTypeFromFile(a):b&&~b.indexOf(";")?b.substr(0,b.indexOf(";")):b},getTypeFromFile:function(a){a=a.split("?")[0];a=a.substring(a.lastIndexOf(".")+1).toLowerCase();return(/(mp4|m4v|ogg|ogv|m3u8|webm|webmv|flv|wmv|mpeg|mov)/gi.test(a)?"video":"audio")+"/"+this.getTypeFromExtension(a)},getTypeFromExtension:function(a){switch(a){case "mp4":case "m4v":case "m4a":return"mp4";case "webm":case "webma":case "webmv":return"webm"; +case "ogg":case "oga":case "ogv":return"ogg";default:return a}},createErrorMessage:function(a,b,c){var d=a.htmlMediaElement,e=document.createElement("div");e.className="me-cannotplay";try{e.style.width=d.width+"px";e.style.height=d.height+"px"}catch(g){}e.innerHTML=b.customError?b.customError:c!==""?'<a href="'+a.url+'"><img src="'+c+'" width="100%" height="100%" /></a>':'<a href="'+a.url+'"><span>'+mejs.i18n.t("Download File")+"</span></a>";d.parentNode.insertBefore(e,d);d.style.display="none";b.error(d)}, +createPlugin:function(a,b,c,d,e,g){c=a.htmlMediaElement;var f=1,i=1,k="me_"+a.method+"_"+mejs.meIndex++,h=new mejs.PluginMediaElement(k,a.method,a.url),j=document.createElement("div"),m;h.tagName=c.tagName;for(m=0;m<c.attributes.length;m++){var q=c.attributes[m];q.specified==true&&h.setAttribute(q.name,q.value)}for(m=c.parentNode;m!==null&&m.tagName.toLowerCase()!=="body"&&m.parentNode!=null;){if(m.parentNode.tagName.toLowerCase()==="p"){m.parentNode.parentNode.insertBefore(m,m.parentNode);break}m= +m.parentNode}if(a.isVideo){f=b.pluginWidth>0?b.pluginWidth:b.videoWidth>0?b.videoWidth:c.getAttribute("width")!==null?c.getAttribute("width"):b.defaultVideoWidth;i=b.pluginHeight>0?b.pluginHeight:b.videoHeight>0?b.videoHeight:c.getAttribute("height")!==null?c.getAttribute("height"):b.defaultVideoHeight;f=mejs.Utility.encodeUrl(f);i=mejs.Utility.encodeUrl(i)}else if(b.enablePluginDebug){f=320;i=240}h.success=b.success;mejs.MediaPluginBridge.registerPluginElement(k,h,c);j.className="me-plugin";j.id= +k+"_container";a.isVideo?c.parentNode.insertBefore(j,c):document.body.insertBefore(j,document.body.childNodes[0]);d=["id="+k,"isvideo="+(a.isVideo?"true":"false"),"autoplay="+(d?"true":"false"),"preload="+e,"width="+f,"startvolume="+b.startVolume,"timerrate="+b.timerRate,"flashstreamer="+b.flashStreamer,"height="+i,"pseudostreamstart="+b.pseudoStreamingStartQueryParam];if(a.url!==null)a.method=="flash"?d.push("file="+mejs.Utility.encodeUrl(a.url)):d.push("file="+a.url);b.enablePluginDebug&&d.push("debug=true"); +b.enablePluginSmoothing&&d.push("smoothing=true");b.enablePseudoStreaming&&d.push("pseudostreaming=true");g&&d.push("controls=true");if(b.pluginVars)d=d.concat(b.pluginVars);switch(a.method){case "silverlight":j.innerHTML='<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="'+k+'" name="'+k+'" width="'+f+'" height="'+i+'" class="mejs-shim"><param name="initParams" value="'+d.join(",")+'" /><param name="windowless" value="true" /><param name="background" value="black" /><param name="minRuntimeVersion" value="3.0.0.0" /><param name="autoUpgrade" value="true" /><param name="source" value="'+ +b.pluginPath+b.silverlightName+'" /></object>';break;case "flash":if(mejs.MediaFeatures.isIE){a=document.createElement("div");j.appendChild(a);a.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" id="'+k+'" width="'+f+'" height="'+i+'" class="mejs-shim"><param name="movie" value="'+b.pluginPath+b.flashName+"?x="+new Date+'" /><param name="flashvars" value="'+d.join("&")+'" /><param name="quality" value="high" /><param name="bgcolor" value="#000000" /><param name="wmode" value="transparent" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /><param name="scale" value="default" /></object>'}else j.innerHTML= +'<embed id="'+k+'" name="'+k+'" play="true" loop="false" quality="high" bgcolor="#000000" wmode="transparent" allowScriptAccess="always" allowFullScreen="true" type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" src="'+b.pluginPath+b.flashName+'" flashvars="'+d.join("&")+'" width="'+f+'" height="'+i+'" scale="default"class="mejs-shim"></embed>';break;case "youtube":if(a.url.lastIndexOf("youtu.be")!=-1){a=a.url.substr(a.url.lastIndexOf("/")+1);if(a.indexOf("?")!= +-1)a=a.substr(0,a.indexOf("?"))}else a=a.url.substr(a.url.lastIndexOf("=")+1);youtubeSettings={container:j,containerId:j.id,pluginMediaElement:h,pluginId:k,videoId:a,height:i,width:f};mejs.PluginDetector.hasPluginVersion("flash",[10,0,0])?mejs.YouTubeApi.createFlash(youtubeSettings):mejs.YouTubeApi.enqueueIframe(youtubeSettings);break;case "vimeo":b=k+"_player";h.vimeoid=a.url.substr(a.url.lastIndexOf("/")+1);j.innerHTML='<iframe src="//player.vimeo.com/video/'+h.vimeoid+"?api=1&portrait=0&byline=0&title=0&player_id="+ +b+'" width="'+f+'" height="'+i+'" frameborder="0" class="mejs-shim" id="'+b+'"></iframe>';if(typeof $f=="function"){var l=$f(j.childNodes[0]);l.addEvent("ready",function(){function o(n,p,r,s){n={type:r,target:p};if(r=="timeupdate"){p.currentTime=n.currentTime=s.seconds;p.duration=n.duration=s.duration}p.dispatchEvent(n.type,n)}$.extend(l,{playVideo:function(){l.api("play")},stopVideo:function(){l.api("unload")},pauseVideo:function(){l.api("pause")},seekTo:function(n){l.api("seekTo",n)},setVolume:function(n){l.api("setVolume", +n)},setMuted:function(n){if(n){l.lastVolume=l.api("getVolume");l.api("setVolume",0)}else{l.api("setVolume",l.lastVolume);delete l.lastVolume}}});l.addEvent("play",function(){o(l,h,"play");o(l,h,"playing")});l.addEvent("pause",function(){o(l,h,"pause")});l.addEvent("finish",function(){o(l,h,"ended")});l.addEvent("playProgress",function(n){o(l,h,"timeupdate",n)});h.pluginElement=j;h.pluginApi=l;mejs.MediaPluginBridge.initPlugin(k)})}else console.warn("You need to include froogaloop for vimeo to work")}c.style.display= +"none";c.removeAttribute("autoplay");return h},updateNative:function(a,b){var c=a.htmlMediaElement,d;for(d in mejs.HtmlMediaElement)c[d]=mejs.HtmlMediaElement[d];b.success(c,c);return c}}; +mejs.YouTubeApi={isIframeStarted:false,isIframeLoaded:false,loadIframeApi:function(){if(!this.isIframeStarted){var a=document.createElement("script");a.src="//www.youtube.com/player_api";var b=document.getElementsByTagName("script")[0];b.parentNode.insertBefore(a,b);this.isIframeStarted=true}},iframeQueue:[],enqueueIframe:function(a){if(this.isLoaded)this.createIframe(a);else{this.loadIframeApi();this.iframeQueue.push(a)}},createIframe:function(a){var b=a.pluginMediaElement,c=new YT.Player(a.containerId, +{height:a.height,width:a.width,videoId:a.videoId,playerVars:{controls:0},events:{onReady:function(){a.pluginMediaElement.pluginApi=c;mejs.MediaPluginBridge.initPlugin(a.pluginId);setInterval(function(){mejs.YouTubeApi.createEvent(c,b,"timeupdate")},250)},onStateChange:function(d){mejs.YouTubeApi.handleStateChange(d.data,c,b)}}})},createEvent:function(a,b,c){c={type:c,target:b};if(a&&a.getDuration){b.currentTime=c.currentTime=a.getCurrentTime();b.duration=c.duration=a.getDuration();c.paused=b.paused; +c.ended=b.ended;c.muted=a.isMuted();c.volume=a.getVolume()/100;c.bytesTotal=a.getVideoBytesTotal();c.bufferedBytes=a.getVideoBytesLoaded();var d=c.bufferedBytes/c.bytesTotal*c.duration;c.target.buffered=c.buffered={start:function(){return 0},end:function(){return d},length:1}}b.dispatchEvent(c.type,c)},iFrameReady:function(){for(this.isIframeLoaded=this.isLoaded=true;this.iframeQueue.length>0;)this.createIframe(this.iframeQueue.pop())},flashPlayers:{},createFlash:function(a){this.flashPlayers[a.pluginId]= +a;var b,c="//www.youtube.com/apiplayer?enablejsapi=1&playerapiid="+a.pluginId+"&version=3&autoplay=0&controls=0&modestbranding=1&loop=0";if(mejs.MediaFeatures.isIE){b=document.createElement("div");a.container.appendChild(b);b.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" id="'+a.pluginId+'" width="'+a.width+'" height="'+a.height+'" class="mejs-shim"><param name="movie" value="'+ +c+'" /><param name="wmode" value="transparent" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /></object>'}else a.container.innerHTML='<object type="application/x-shockwave-flash" id="'+a.pluginId+'" data="'+c+'" width="'+a.width+'" height="'+a.height+'" style="visibility: visible; " class="mejs-shim"><param name="allowScriptAccess" value="always"><param name="wmode" value="transparent"></object>'},flashReady:function(a){var b=this.flashPlayers[a],c= +document.getElementById(a),d=b.pluginMediaElement;d.pluginApi=d.pluginElement=c;mejs.MediaPluginBridge.initPlugin(a);c.cueVideoById(b.videoId);a=b.containerId+"_callback";window[a]=function(e){mejs.YouTubeApi.handleStateChange(e,c,d)};c.addEventListener("onStateChange",a);setInterval(function(){mejs.YouTubeApi.createEvent(c,d,"timeupdate")},250);mejs.YouTubeApi.createEvent(c,d,"canplay")},handleStateChange:function(a,b,c){switch(a){case -1:c.paused=true;c.ended=true;mejs.YouTubeApi.createEvent(b, +c,"loadedmetadata");break;case 0:c.paused=false;c.ended=true;mejs.YouTubeApi.createEvent(b,c,"ended");break;case 1:c.paused=false;c.ended=false;mejs.YouTubeApi.createEvent(b,c,"play");mejs.YouTubeApi.createEvent(b,c,"playing");break;case 2:c.paused=true;c.ended=false;mejs.YouTubeApi.createEvent(b,c,"pause");break;case 3:mejs.YouTubeApi.createEvent(b,c,"progress")}}};function onYouTubePlayerAPIReady(){mejs.YouTubeApi.iFrameReady()}function onYouTubePlayerReady(a){mejs.YouTubeApi.flashReady(a)} +window.mejs=mejs;window.MediaElement=mejs.MediaElement; +(function(a,b){var c={locale:{language:"",strings:{}},methods:{}};c.getLanguage=function(){return(c.locale.language||window.navigator.userLanguage||window.navigator.language).substr(0,2).toLowerCase()};if(typeof mejsL10n!="undefined")c.locale.language=mejsL10n.language;c.methods.checkPlain=function(d){var e,g,f={"&":"&",'"':""","<":"<",">":">"};d=String(d);for(e in f)if(f.hasOwnProperty(e)){g=RegExp(e,"g");d=d.replace(g,f[e])}return d};c.methods.t=function(d,e){if(c.locale.strings&& +c.locale.strings[e.context]&&c.locale.strings[e.context][d])d=c.locale.strings[e.context][d];return c.methods.checkPlain(d)};c.t=function(d,e){if(typeof d==="string"&&d.length>0){var g=c.getLanguage();e=e||{context:g};return c.methods.t(d,e)}else throw{name:"InvalidArgumentException",message:"First argument is either not a string or empty."};};b.i18n=c})(document,mejs);(function(a){if(typeof mejsL10n!="undefined")a[mejsL10n.language]=mejsL10n.strings})(mejs.i18n.locale.strings); +(function(a){if(typeof a.de==="undefined")a.de={Fullscreen:"Vollbild","Go Fullscreen":"Vollbild an","Turn off Fullscreen":"Vollbild aus",Close:"Schlie\u00dfen"}})(mejs.i18n.locale.strings);(function(a){if(typeof a.zh==="undefined")a.zh={Fullscreen:"\u5168\u87a2\u5e55","Go Fullscreen":"\u5168\u5c4f\u6a21\u5f0f","Turn off Fullscreen":"\u9000\u51fa\u5168\u5c4f\u6a21\u5f0f",Close:"\u95dc\u9589"}})(mejs.i18n.locale.strings); diff --git a/js/mediaelement/build/mediaelementplayer.css b/js/mediaelement/build/mediaelementplayer.css new file mode 100644 index 0000000000000000000000000000000000000000..432ef5c4e966f3a43ef4dbca9e4a448a7ede764a --- /dev/null +++ b/js/mediaelement/build/mediaelementplayer.css @@ -0,0 +1,954 @@ +.mejs-container { + position: relative; + background: #000; + font-family: Helvetica, Arial; + text-align: left; + vertical-align: top; + text-indent: 0; +} + +.me-plugin { + position: absolute; + height: auto; + width: auto; +} + +.mejs-embed, .mejs-embed body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + background: #000; + overflow: hidden; +} + +.mejs-fullscreen { + /* set it to not show scroll bars so 100% will work */ + overflow: hidden !important; +} + +.mejs-container-fullscreen { + position: fixed; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + z-index: 1000; +} +.mejs-container-fullscreen .mejs-mediaelement, +.mejs-container-fullscreen video { + width: 100%; + height: 100%; +} + +.mejs-clear { + clear: both; +} + +/* Start: LAYERS */ +.mejs-background { + position: absolute; + top: 0; + left: 0; +} + +.mejs-mediaelement { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.mejs-poster { + position: absolute; + top: 0; + left: 0; + background-size: contain ; + background-position: 50% 50% ; + background-repeat: no-repeat ; +} +:root .mejs-poster img { + display: none ; +} + +.mejs-poster img { + border: 0; + padding: 0; + border: 0; +} + +.mejs-overlay { + position: absolute; + top: 0; + left: 0; +} + +.mejs-overlay-play { + cursor: pointer; +} + +.mejs-overlay-button { + position: absolute; + top: 50%; + left: 50%; + width: 100px; + height: 100px; + margin: -50px 0 0 -50px; + background: url(bigplay.svg) no-repeat; +} + +.no-svg .mejs-overlay-button { + background-image: url(bigplay.png); +} + +.mejs-overlay:hover .mejs-overlay-button { + background-position: 0 -100px ; +} + +.mejs-overlay-loading { + position: absolute; + top: 50%; + left: 50%; + width: 80px; + height: 80px; + margin: -40px 0 0 -40px; + background: #333; + background: url(background.png); + background: rgba(0, 0, 0, 0.9); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.9)), to(rgba(0,0,0,0.9))); + background: -webkit-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: -moz-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: -o-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: -ms-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: linear-gradient(rgba(50,50,50,0.9), rgba(0,0,0,0.9)); +} + +.mejs-overlay-loading span { + display: block; + width: 80px; + height: 80px; + background: transparent url(loading.gif) 50% 50% no-repeat; +} + +/* End: LAYERS */ + +/* Start: CONTROL BAR */ +.mejs-container .mejs-controls { + position: absolute; + list-style-type: none; + margin: 0; + padding: 0; + bottom: 0; + left: 0; + background: url(background.png); + background: rgba(0, 0, 0, 0.7); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.7)), to(rgba(0,0,0,0.7))); + background: -webkit-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -moz-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -o-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -ms-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: linear-gradient(rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + height: 30px; + width: 100%; +} +.mejs-container .mejs-controls div { + list-style-type: none; + background-image: none; + display: block; + float: left; + margin: 0; + padding: 0; + width: 26px; + height: 26px; + font-size: 11px; + line-height: 11px; + font-family: Helvetica, Arial; + border: 0; +} + +.mejs-controls .mejs-button button { + cursor: pointer; + display: block; + font-size: 0; + line-height: 0; + text-decoration: none; + margin: 7px 5px; + padding: 0; + position: absolute; + height: 16px; + width: 16px; + border: 0; + background: transparent url(controls.svg) no-repeat; +} + +.no-svg .mejs-controls .mejs-button button { + background-image: url(controls.png); +} + +/* :focus for accessibility */ +.mejs-controls .mejs-button button:focus { + outline: dotted 1px #999; +} + +/* End: CONTROL BAR */ + +/* Start: Time (Current / Duration) */ +.mejs-container .mejs-controls .mejs-time { + color: #fff; + display: block; + height: 17px; + width: auto; + padding: 8px 3px 0 3px ; + overflow: hidden; + text-align: center; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.mejs-container .mejs-controls .mejs-time span { + color: #fff; + font-size: 11px; + line-height: 12px; + display: block; + float: left; + margin: 1px 2px 0 0; + width: auto; +} +/* End: Time (Current / Duration) */ + +/* Start: Play/Pause/Stop */ +.mejs-controls .mejs-play button { + background-position: 0 0; +} + +.mejs-controls .mejs-pause button { + background-position: 0 -16px; +} + +.mejs-controls .mejs-stop button { + background-position: -112px 0; +} +/* Start: Play/Pause/Stop */ + +/* Start: Progress Bar */ +.mejs-controls div.mejs-time-rail { + direction: ltr; + width: 200px; + padding-top: 5px; +} + +.mejs-controls .mejs-time-rail span { + display: block; + position: absolute; + width: 180px; + height: 10px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + cursor: pointer; +} + +.mejs-controls .mejs-time-rail .mejs-time-total { + margin: 5px; + background: #333; + background: rgba(50,50,50,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(30,30,30,0.8)), to(rgba(60,60,60,0.8))); + background: -webkit-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -moz-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -o-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -ms-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: linear-gradient(rgba(30,30,30,0.8), rgba(60,60,60,0.8)); +} + +.mejs-controls .mejs-time-rail .mejs-time-buffering { + width: 100%; + background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 15px 15px; + -moz-background-size: 15px 15px; + -o-background-size: 15px 15px; + background-size: 15px 15px; + -webkit-animation: buffering-stripes 2s linear infinite; + -moz-animation: buffering-stripes 2s linear infinite; + -ms-animation: buffering-stripes 2s linear infinite; + -o-animation: buffering-stripes 2s linear infinite; + animation: buffering-stripes 2s linear infinite; +} + +@-webkit-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@-moz-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@-ms-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@-o-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } + +.mejs-controls .mejs-time-rail .mejs-time-loaded { + background: #3caac8; + background: rgba(60,170,200,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(44,124,145,0.8)), to(rgba(78,183,212,0.8))); + background: -webkit-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: -moz-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: -o-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: -ms-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: linear-gradient(rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + width: 0; +} + +.mejs-controls .mejs-time-rail .mejs-time-current { + background: #fff; + background: rgba(255,255,255,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,0.9)), to(rgba(200,200,200,0.8))); + background: -webkit-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -moz-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -o-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -ms-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: linear-gradient(rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + width: 0; +} + +.mejs-controls .mejs-time-rail .mejs-time-handle { + display: none; + position: absolute; + margin: 0; + width: 10px; + background: #fff; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + cursor: pointer; + border: solid 2px #333; + top: -2px; + text-align: center; +} + +.mejs-controls .mejs-time-rail .mejs-time-float { + position: absolute; + display: none; + background: #eee; + width: 36px; + height: 17px; + border: solid 1px #333; + top: -26px; + margin-left: -18px; + text-align: center; + color: #111; +} + +.mejs-controls .mejs-time-rail .mejs-time-float-current { + margin: 2px; + width: 30px; + display: block; + text-align: center; + left: 0; +} + +.mejs-controls .mejs-time-rail .mejs-time-float-corner { + position: absolute; + display: block; + width: 0; + height: 0; + line-height: 0; + border: solid 5px #eee; + border-color: #eee transparent transparent transparent; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + top: 15px; + left: 13px; +} + +.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float { + width: 48px; +} + +.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float-current { + width: 44px; +} + +.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float-corner { + left: 18px; +} + +/* +.mejs-controls .mejs-time-rail:hover .mejs-time-handle { + visibility:visible; +} +*/ +/* End: Progress Bar */ + +/* Start: Fullscreen */ +.mejs-controls .mejs-fullscreen-button button { + background-position: -32px 0; +} + +.mejs-controls .mejs-unfullscreen button { + background-position: -32px -16px; +} +/* End: Fullscreen */ + + +/* Start: Mute/Volume */ +.mejs-controls .mejs-volume-button { +} + +.mejs-controls .mejs-mute button { + background-position: -16px -16px; +} + +.mejs-controls .mejs-unmute button { + background-position: -16px 0; +} + +.mejs-controls .mejs-volume-button { + position: relative; +} + +.mejs-controls .mejs-volume-button .mejs-volume-slider { + display: none; + height: 115px; + width: 25px; + background: url(background.png); + background: rgba(50, 50, 50, 0.7); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + top: -115px; + left: 0; + z-index: 1; + position: absolute; + margin: 0; +} + +.mejs-controls .mejs-volume-button:hover { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +/* +.mejs-controls .mejs-volume-button:hover .mejs-volume-slider { + display: block; +} +*/ + +.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-total { + position: absolute; + left: 11px; + top: 8px; + width: 2px; + height: 100px; + background: #ddd; + background: rgba(255, 255, 255, 0.5); + margin: 0; +} + +.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-current { + position: absolute; + left: 11px; + top: 8px; + width: 2px; + height: 100px; + background: #ddd; + background: rgba(255, 255, 255, 0.9); + margin: 0; +} + +.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-handle { + position: absolute; + left: 4px; + top: -3px; + width: 16px; + height: 6px; + background: #ddd; + background: rgba(255, 255, 255, 0.9); + cursor: N-resize; + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; + margin: 0; +} + +/* horizontal version */ +.mejs-controls div.mejs-horizontal-volume-slider { + height: 26px; + width: 60px; + position: relative; +} + +.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-total { + position: absolute; + left: 0; + top: 11px; + width: 50px; + height: 8px; + margin: 0; + padding: 0; + font-size: 1px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + background: #333; + background: rgba(50,50,50,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(30,30,30,0.8)), to(rgba(60,60,60,0.8))); + background: -webkit-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -moz-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -o-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -ms-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: linear-gradient(rgba(30,30,30,0.8), rgba(60,60,60,0.8)); +} + +.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-current { + position: absolute; + left: 0; + top: 11px; + width: 50px; + height: 8px; + margin: 0; + padding: 0; + font-size: 1px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + background: #fff; + background: rgba(255,255,255,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,0.9)), to(rgba(200,200,200,0.8))); + background: -webkit-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -moz-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -o-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -ms-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: linear-gradient(rgba(255,255,255,0.9), rgba(200,200,200,0.8)); +} + +.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-handle { + display: none; +} + +/* End: Mute/Volume */ + +/* Start: Track (Captions and Chapters) */ +.mejs-controls .mejs-captions-button { + position: relative; +} + +.mejs-controls .mejs-captions-button button { + background-position: -48px 0; +} +.mejs-controls .mejs-captions-button .mejs-captions-selector { + visibility: hidden; + position: absolute; + bottom: 26px; + right: -51px; + width: 85px; + height: 100px; + background: url(background.png); + background: rgba(50,50,50,0.7); + border: solid 1px transparent; + padding: 10px 10px 0 10px; + overflow: hidden; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +/* +.mejs-controls .mejs-captions-button:hover .mejs-captions-selector { + visibility: visible; +} +*/ + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul { + margin: 0; + padding: 0; + display: block; + list-style-type: none !important; + overflow: hidden; +} + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul li { + margin: 0 0 6px 0; + padding: 0; + list-style-type: none !important; + display: block; + color: #fff; + overflow: hidden; +} + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul li input { + clear: both; + float: left; + margin: 3px 3px 0 5px; +} + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul li label { + width: 55px; + float: left; + padding: 4px 0 0 0; + line-height: 15px; + font-family: helvetica, arial; + font-size: 10px; +} + +.mejs-controls .mejs-captions-button .mejs-captions-translations { + font-size: 10px; + margin: 0 0 5px 0; +} + +.mejs-chapters { + position: absolute; + top: 0; + left: 0; + -xborder-right: solid 1px #fff; + width: 10000px; + z-index: 1; +} + +.mejs-chapters .mejs-chapter { + position: absolute; + float: left; + background: #222; + background: rgba(0, 0, 0, 0.7); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.7)), to(rgba(0,0,0,0.7))); + background: -webkit-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -moz-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -o-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -ms-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: linear-gradient(rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, startColorstr=#323232,endColorstr=#000000); + overflow: hidden; + border: 0; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block { + font-size: 11px; + color: #fff; + padding: 5px; + display: block; + border-right: solid 1px #333; + border-bottom: solid 1px #333; + cursor: pointer; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block-last { + border-right: none; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block:hover { + background: #666; + background: rgba(102,102,102, 0.7); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(102,102,102,0.7)), to(rgba(50,50,50,0.6))); + background: -webkit-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: -moz-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: -o-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: -ms-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: linear-gradient(rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, startColorstr=#666666,endColorstr=#323232); +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block .ch-title { + font-size: 12px; + font-weight: bold; + display: block; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0 0 3px 0; + line-height: 12px; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block .ch-timespan { + font-size: 12px; + line-height: 12px; + margin: 3px 0 4px 0; + display: block; + white-space: nowrap; + text-overflow: ellipsis; +} + +.mejs-captions-layer { + position: absolute; + bottom: 0; + left: 0; + text-align:center; + line-height: 20px; + font-size: 16px; + color: #fff; +} + +.mejs-captions-layer a { + color: #fff; + text-decoration: underline; +} + +.mejs-captions-layer[lang=ar] { + font-size: 20px; + font-weight: normal; +} + +.mejs-captions-position { + position: absolute; + width: 100%; + bottom: 15px; + left: 0; +} + +.mejs-captions-position-hover { + bottom: 35px; +} + +.mejs-captions-text { + padding: 3px 5px; + background: url(background.png); + background: rgba(20, 20, 20, 0.5); + white-space: pre-wrap; +} +/* End: Track (Captions and Chapters) */ + +/* Start: Error */ +.me-cannotplay { +} + +.me-cannotplay a { + color: #fff; + font-weight: bold; +} + +.me-cannotplay span { + padding: 15px; + display: block; +} +/* End: Error */ + + +/* Start: Loop */ +.mejs-controls .mejs-loop-off button { + background-position: -64px -16px; +} + +.mejs-controls .mejs-loop-on button { + background-position: -64px 0; +} + +/* End: Loop */ + +/* Start: backlight */ +.mejs-controls .mejs-backlight-off button { + background-position: -80px -16px; +} + +.mejs-controls .mejs-backlight-on button { + background-position: -80px 0; +} +/* End: backlight */ + +/* Start: Picture Controls */ +.mejs-controls .mejs-picturecontrols-button { + background-position: -96px 0; +} +/* End: Picture Controls */ + + +/* context menu */ +.mejs-contextmenu { + position: absolute; + width: 150px; + padding: 10px; + border-radius: 4px; + top: 0; + left: 0; + background: #fff; + border: solid 1px #999; + z-index: 1001; /* make sure it shows on fullscreen */ +} +.mejs-contextmenu .mejs-contextmenu-separator { + height: 1px; + font-size: 0; + margin: 5px 6px; + background: #333; +} + +.mejs-contextmenu .mejs-contextmenu-item { + font-family: Helvetica, Arial; + font-size: 12px; + padding: 4px 6px; + cursor: pointer; + color: #333; +} +.mejs-contextmenu .mejs-contextmenu-item:hover { + background: #2C7C91; + color: #fff; +} + +/* Start: Source Chooser */ +.mejs-controls .mejs-sourcechooser-button { + position: relative; +} + +.mejs-controls .mejs-sourcechooser-button button { + background-position: -128px 0; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector { + visibility: hidden; + position: absolute; + bottom: 26px; + right: -10px; + width: 130px; + height: 100px; + background: url(background.png); + background: rgba(50,50,50,0.7); + border: solid 1px transparent; + padding: 10px; + overflow: hidden; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul { + margin: 0; + padding: 0; + display: block; + list-style-type: none !important; + overflow: hidden; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li { + margin: 0 0 6px 0; + padding: 0; + list-style-type: none !important; + display: block; + color: #fff; + overflow: hidden; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li input { + clear: both; + float: left; + margin: 3px 3px 0 5px; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li label { + width: 100px; + float: left; + padding: 4px 0 0 0; + line-height: 15px; + font-family: helvetica, arial; + font-size: 10px; +} +/* End: Source Chooser */ + +/* Start: Postroll */ +.mejs-postroll-layer { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + background: url(background.png); + background: rgba(50,50,50,0.7); + z-index: 1000; + overflow: hidden; +} +.mejs-postroll-layer-content { + width: 100%; + height: 100%; +} +.mejs-postroll-close { + position: absolute; + right: 0; + top: 0; + background: url(background.png); + background: rgba(50,50,50,0.7); + color: #fff; + padding: 4px; + z-index: 100; + cursor: pointer; +} +/* End: Postroll */ + + +/* Start: Speed */ +div.mejs-speed-button { + width: 46px !important; + position: relative; +} + +.mejs-controls .mejs-button.mejs-speed-button button { + background: transparent; + width: 36px; + font-size: 11px; + line-height: normal; + color: #ffffff; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector { + visibility: hidden; + position: absolute; + top: -100px; + left: -10px; + width: 60px; + height: 100px; + background: url(background.png); + background: rgba(50, 50, 50, 0.7); + border: solid 1px transparent; + padding: 0; + overflow: hidden; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.mejs-controls .mejs-speed-button:hover > .mejs-speed-selector { + visibility: visible; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li label.mejs-speed-selected { + color: rgba(33, 248, 248, 1); +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul { + margin: 0; + padding: 0; + display: block; + list-style-type: none !important; + overflow: hidden; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li { + margin: 0 0 6px 0; + padding: 0 10px; + list-style-type: none !important; + display: block; + color: #fff; + overflow: hidden; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li input { + clear: both; + float: left; + margin: 3px 3px 0 5px; + display: none; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li label { + width: 60px; + float: left; + padding: 4px 0 0 0; + line-height: 15px; + font-family: helvetica, arial; + font-size: 11.5px; + color: white; + margin-left: 5px; + cursor: pointer; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li:hover { + background-color: rgb(200, 200, 200) !important; + background-color: rgba(255,255,255,.4) !important; +} +/* End: Speed */ diff --git a/js/mediaelement/build/mediaelementplayer.js b/js/mediaelement/build/mediaelementplayer.js new file mode 100644 index 0000000000000000000000000000000000000000..c1c1622f8b1a86c87a3367425822db98bbbe978d --- /dev/null +++ b/js/mediaelement/build/mediaelementplayer.js @@ -0,0 +1,3328 @@ +/*! + * MediaElementPlayer + * http://mediaelementjs.com/ + * + * Creates a controller bar for HTML5 <video> add <audio> tags + * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) + * + * Copyright 2010-2013, John Dyer (http://j.hn/) + * License: MIT + * + */ +if (typeof jQuery != 'undefined') { + mejs.$ = jQuery; +} else if (typeof ender != 'undefined') { + mejs.$ = ender; +} +(function ($) { + + // default player values + mejs.MepDefaults = { + // url to poster (to fix iOS 3.x) + poster: '', + // When the video is ended, we can show the poster. + showPosterWhenEnded: false, + // default if the <video width> is not specified + defaultVideoWidth: 480, + // default if the <video height> is not specified + defaultVideoHeight: 270, + // if set, overrides <video width> + videoWidth: -1, + // if set, overrides <video height> + videoHeight: -1, + // default if the user doesn't specify + defaultAudioWidth: 400, + // default if the user doesn't specify + defaultAudioHeight: 30, + + // default amount to move back when back key is pressed + defaultSeekBackwardInterval: function(media) { + return (media.duration * 0.05); + }, + // default amount to move forward when forward key is pressed + defaultSeekForwardInterval: function(media) { + return (media.duration * 0.05); + }, + + // set dimensions via JS instead of CSS + setDimensions: true, + + // width of audio player + audioWidth: -1, + // height of audio player + audioHeight: -1, + // initial volume when the player starts (overrided by user cookie) + startVolume: 0.8, + // useful for <audio> player loops + loop: false, + // rewind to beginning when media ends + autoRewind: true, + // resize to media dimensions + enableAutosize: true, + // forces the hour marker (##:00:00) + alwaysShowHours: false, + + // show framecount in timecode (##:00:00:00) + showTimecodeFrameCount: false, + // used when showTimecodeFrameCount is set to true + framesPerSecond: 25, + + // automatically calculate the width of the progress bar based on the sizes of other elements + autosizeProgress : true, + // Hide controls when playing and mouse is not over the video + alwaysShowControls: false, + // Display the video control + hideVideoControlsOnLoad: false, + // Enable click video element to toggle play/pause + clickToPlayPause: true, + // force iPad's native controls + iPadUseNativeControls: false, + // force iPhone's native controls + iPhoneUseNativeControls: false, + // force Android's native controls + AndroidUseNativeControls: false, + // features to show + features: ['playpause','current','progress','duration','tracks','volume','fullscreen'], + // only for dynamic + isVideo: true, + + // turns keyboard support on and off for this instance + enableKeyboard: true, + + // whenthis player starts, it will pause other players + pauseOtherPlayers: true, + + // array of keyboard actions such as play pause + keyActions: [ + { + keys: [ + 32, // SPACE + 179 // GOOGLE play/pause button + ], + action: function(player, media) { + if (media.paused || media.ended) { + player.play(); + } else { + player.pause(); + } + } + }, + { + keys: [38], // UP + action: function(player, media) { + player.container.find('.mejs-volume-slider').css('display','block'); + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + var newVolume = Math.min(media.volume + 0.1, 1); + media.setVolume(newVolume); + } + }, + { + keys: [40], // DOWN + action: function(player, media) { + player.container.find('.mejs-volume-slider').css('display','block'); + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + var newVolume = Math.max(media.volume - 0.1, 0); + media.setVolume(newVolume); + } + }, + { + keys: [ + 37, // LEFT + 227 // Google TV rewind + ], + action: function(player, media) { + if (!isNaN(media.duration) && media.duration > 0) { + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + // 5% + var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0); + media.setCurrentTime(newTime); + } + } + }, + { + keys: [ + 39, // RIGHT + 228 // Google TV forward + ], + action: function(player, media) { + if (!isNaN(media.duration) && media.duration > 0) { + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + // 5% + var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration); + media.setCurrentTime(newTime); + } + } + }, + { + keys: [70], // F + action: function(player, media) { + if (typeof player.enterFullScreen != 'undefined') { + if (player.isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + } + } + }, + { + keys: [77], // M + action: function(player, media) { + player.container.find('.mejs-volume-slider').css('display','block'); + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + if (player.media.muted) { + player.setMuted(false); + } else { + player.setMuted(true); + } + } + } + ] + }; + + mejs.mepIndex = 0; + + mejs.players = {}; + + // wraps a MediaElement object in player controls + mejs.MediaElementPlayer = function(node, o) { + // enforce object, even without "new" (via John Resig) + if ( !(this instanceof mejs.MediaElementPlayer) ) { + return new mejs.MediaElementPlayer(node, o); + } + + var t = this; + + // these will be reset after the MediaElement.success fires + t.$media = t.$node = $(node); + t.node = t.media = t.$media[0]; + + // check for existing player + if (typeof t.node.player != 'undefined') { + return t.node.player; + } else { + // attach player to DOM node for reference + t.node.player = t; + } + + + // try to get options from data-mejsoptions + if (typeof o == 'undefined') { + o = t.$node.data('mejsoptions'); + } + + // extend default options + t.options = $.extend({},mejs.MepDefaults,o); + + // unique ID + t.id = 'mep_' + mejs.mepIndex++; + + // add to player array (for focus events) + mejs.players[t.id] = t; + + // start up + t.init(); + + return t; + }; + + // actual player + mejs.MediaElementPlayer.prototype = { + + hasFocus: false, + + controlsAreVisible: true, + + init: function() { + + var + t = this, + mf = mejs.MediaFeatures, + // options for MediaElement (shim) + meOptions = $.extend(true, {}, t.options, { + success: function(media, domNode) { t.meReady(media, domNode); }, + error: function(e) { t.handleError(e);} + }), + tagName = t.media.tagName.toLowerCase(); + + t.isDynamic = (tagName !== 'audio' && tagName !== 'video'); + + if (t.isDynamic) { + // get video from src or href? + t.isVideo = t.options.isVideo; + } else { + t.isVideo = (tagName !== 'audio' && t.options.isVideo); + } + + // use native controls in iPad, iPhone, and Android + if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) { + + // add controls and stop + t.$media.attr('controls', 'controls'); + + // attempt to fix iOS 3 bug + //t.$media.removeAttr('poster'); + // no Issue found on iOS3 -ttroxell + + // override Apple's autoplay override for iPads + if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { + t.play(); + } + + } else if (mf.isAndroid && t.options.AndroidUseNativeControls) { + + // leave default player + + } else { + + // DESKTOP: use MediaElementPlayer controls + + // remove native controls + t.$media.removeAttr('controls'); + + // build container + t.container = + $('<div id="' + t.id + '" class="mejs-container ' + (mejs.MediaFeatures.svg ? 'svg' : 'no-svg') + '">'+ + '<div class="mejs-inner">'+ + '<div class="mejs-mediaelement"></div>'+ + '<div class="mejs-layers"></div>'+ + '<div class="mejs-controls"></div>'+ + '<div class="mejs-clear"></div>'+ + '</div>' + + '</div>') + .addClass(t.$media[0].className) + .insertBefore(t.$media); + + // add classes for user and content + t.container.addClass( + (mf.isAndroid ? 'mejs-android ' : '') + + (mf.isiOS ? 'mejs-ios ' : '') + + (mf.isiPad ? 'mejs-ipad ' : '') + + (mf.isiPhone ? 'mejs-iphone ' : '') + + (t.isVideo ? 'mejs-video ' : 'mejs-audio ') + ); + + + // move the <video/video> tag into the right spot + if (mf.isiOS) { + + // sadly, you can't move nodes in iOS, so we have to destroy and recreate it! + var $newMedia = t.$media.clone(); + + t.container.find('.mejs-mediaelement').append($newMedia); + + t.$media.remove(); + t.$node = t.$media = $newMedia; + t.node = t.media = $newMedia[0] + + } else { + + // normal way of moving it into place (doesn't work on iOS) + t.container.find('.mejs-mediaelement').append(t.$media); + } + + // find parts + t.controls = t.container.find('.mejs-controls'); + t.layers = t.container.find('.mejs-layers'); + + // determine the size + + /* size priority: + (1) videoWidth (forced), + (2) style="width;height;" + (3) width attribute, + (4) defaultVideoWidth (for unspecified cases) + */ + + var tagType = (t.isVideo ? 'video' : 'audio'), + capsTagName = tagType.substring(0,1).toUpperCase() + tagType.substring(1); + + + + if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) { + t.width = t.options[tagType + 'Width']; + } else if (t.media.style.width !== '' && t.media.style.width !== null) { + t.width = t.media.style.width; + } else if (t.media.getAttribute('width') !== null) { + t.width = t.$media.attr('width'); + } else { + t.width = t.options['default' + capsTagName + 'Width']; + } + + if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) { + t.height = t.options[tagType + 'Height']; + } else if (t.media.style.height !== '' && t.media.style.height !== null) { + t.height = t.media.style.height; + } else if (t.$media[0].getAttribute('height') !== null) { + t.height = t.$media.attr('height'); + } else { + t.height = t.options['default' + capsTagName + 'Height']; + } + + // set the size, while we wait for the plugins to load below + t.setPlayerSize(t.width, t.height); + + // create MediaElementShim + meOptions.pluginWidth = t.width; + meOptions.pluginHeight = t.height; + } + + // create MediaElement shim + mejs.MediaElement(t.$media[0], meOptions); + + if (typeof(t.container) != 'undefined' && t.controlsAreVisible){ + // controls are shown when loaded + t.container.trigger('controlsshown'); + } + }, + + showControls: function(doAnimation) { + var t = this; + + doAnimation = typeof doAnimation == 'undefined' || doAnimation; + + if (t.controlsAreVisible) + return; + + if (doAnimation) { + t.controls + .css('visibility','visible') + .stop(true, true).fadeIn(200, function() { + t.controlsAreVisible = true; + t.container.trigger('controlsshown'); + }); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control') + .css('visibility','visible') + .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); + + } else { + t.controls + .css('visibility','visible') + .css('display','block'); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control') + .css('visibility','visible') + .css('display','block'); + + t.controlsAreVisible = true; + t.container.trigger('controlsshown'); + } + + t.setControlsSize(); + + }, + + hideControls: function(doAnimation) { + var t = this; + + doAnimation = typeof doAnimation == 'undefined' || doAnimation; + + if (!t.controlsAreVisible || t.options.alwaysShowControls) + return; + + if (doAnimation) { + // fade out main controls + t.controls.stop(true, true).fadeOut(200, function() { + $(this) + .css('visibility','hidden') + .css('display','block'); + + t.controlsAreVisible = false; + t.container.trigger('controlshidden'); + }); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() { + $(this) + .css('visibility','hidden') + .css('display','block'); + }); + } else { + + // hide main controls + t.controls + .css('visibility','hidden') + .css('display','block'); + + // hide others + t.container.find('.mejs-control') + .css('visibility','hidden') + .css('display','block'); + + t.controlsAreVisible = false; + t.container.trigger('controlshidden'); + } + }, + + controlsTimer: null, + + startControlsTimer: function(timeout) { + + var t = this; + + timeout = typeof timeout != 'undefined' ? timeout : 1500; + + t.killControlsTimer('start'); + + t.controlsTimer = setTimeout(function() { + // + t.hideControls(); + t.killControlsTimer('hide'); + }, timeout); + }, + + killControlsTimer: function(src) { + + var t = this; + + if (t.controlsTimer !== null) { + clearTimeout(t.controlsTimer); + delete t.controlsTimer; + t.controlsTimer = null; + } + }, + + controlsEnabled: true, + + disableControls: function() { + var t= this; + + t.killControlsTimer(); + t.hideControls(false); + this.controlsEnabled = false; + }, + + enableControls: function() { + var t= this; + + t.showControls(false); + + t.controlsEnabled = true; + }, + + + // Sets up all controls and events + meReady: function(media, domNode) { + + + var t = this, + mf = mejs.MediaFeatures, + autoplayAttr = domNode.getAttribute('autoplay'), + autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'), + featureIndex, + feature; + + // make sure it can't create itself again if a plugin reloads + if (t.created) { + return; + } else { + t.created = true; + } + + t.media = media; + t.domNode = domNode; + + if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) { + + // two built in features + t.buildposter(t, t.controls, t.layers, t.media); + t.buildkeyboard(t, t.controls, t.layers, t.media); + t.buildoverlays(t, t.controls, t.layers, t.media); + + // grab for use by features + t.findTracks(); + + // add user-defined features/controls + for (featureIndex in t.options.features) { + feature = t.options.features[featureIndex]; + if (t['build' + feature]) { + try { + t['build' + feature](t, t.controls, t.layers, t.media); + } catch (e) { + // TODO: report control error + //throw e; + + + } + } + } + + t.container.trigger('controlsready'); + + // reset all layers and controls + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + + + // controls fade + if (t.isVideo) { + + if (mejs.MediaFeatures.hasTouch) { + + // for touch devices (iOS, Android) + // show/hide without animation on touch + + t.$media.bind('touchstart', function() { + + + // toggle controls + if (t.controlsAreVisible) { + t.hideControls(false); + } else { + if (t.controlsEnabled) { + t.showControls(false); + } + } + }); + + } else { + + // create callback here since it needs access to current + // MediaElement object + t.clickToPlayPauseCallback = function() { + // + + if (t.options.clickToPlayPause) { + if (t.media.paused) { + t.play(); + } else { + t.pause(); + } + } + }; + + // click to play/pause + t.media.addEventListener('click', t.clickToPlayPauseCallback, false); + + // show/hide controls + t.container + .bind('mouseenter mouseover', function () { + if (t.controlsEnabled) { + if (!t.options.alwaysShowControls ) { + t.killControlsTimer('enter'); + t.showControls(); + t.startControlsTimer(2500); + } + } + }) + .bind('mousemove', function() { + if (t.controlsEnabled) { + if (!t.controlsAreVisible) { + t.showControls(); + } + if (!t.options.alwaysShowControls) { + t.startControlsTimer(2500); + } + } + }) + .bind('mouseleave', function () { + if (t.controlsEnabled) { + if (!t.media.paused && !t.options.alwaysShowControls) { + t.startControlsTimer(1000); + } + } + }); + } + + if(t.options.hideVideoControlsOnLoad) { + t.hideControls(false); + } + + // check for autoplay + if (autoplay && !t.options.alwaysShowControls) { + t.hideControls(); + } + + // resizer + if (t.options.enableAutosize) { + t.media.addEventListener('loadedmetadata', function(e) { + // if the <video height> was not set and the options.videoHeight was not set + // then resize to the real dimensions + if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) { + t.setPlayerSize(e.target.videoWidth, e.target.videoHeight); + t.setControlsSize(); + t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight); + } + }, false); + } + } + + // EVENTS + + // FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them) + media.addEventListener('play', function() { + var playerIndex; + + // go through all other players + for (playerIndex in mejs.players) { + var p = mejs.players[playerIndex]; + if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) { + p.pause(); + } + p.hasFocus = false; + } + + t.hasFocus = true; + },false); + + + // ended for all + t.media.addEventListener('ended', function (e) { + if(t.options.autoRewind) { + try{ + t.media.setCurrentTime(0); + } catch (exp) { + + } + } + t.media.pause(); + + if (t.setProgressRail) { + t.setProgressRail(); + } + if (t.setCurrentRail) { + t.setCurrentRail(); + } + + if (t.options.loop) { + t.play(); + } else if (!t.options.alwaysShowControls && t.controlsEnabled) { + t.showControls(); + } + }, false); + + // resize on the first play + t.media.addEventListener('loadedmetadata', function(e) { + if (t.updateDuration) { + t.updateDuration(); + } + if (t.updateCurrent) { + t.updateCurrent(); + } + + if (!t.isFullScreen) { + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + } + }, false); + + + // webkit has trouble doing this without a delay + setTimeout(function () { + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + }, 50); + + // adjust controls whenever window sizes (used to be in fullscreen only) + t.globalBind('resize', function() { + + // don't resize for fullscreen mode + if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) { + t.setPlayerSize(t.width, t.height); + } + + // always adjust controls + t.setControlsSize(); + }); + + // TEMP: needs to be moved somewhere else + if (t.media.pluginType == 'youtube' && t.options.autoplay) { + //LOK-Soft: added t.options.autoplay to if -- I can only guess this is for hiding play button when autoplaying youtube, general hiding play button layer causes missing button on player load + t.container.find('.mejs-overlay-play').hide(); + } + } + + // force autoplay for HTML5 + if (autoplay && media.pluginType == 'native') { + t.play(); + } + + + if (t.options.success) { + + if (typeof t.options.success == 'string') { + window[t.options.success](t.media, t.domNode, t); + } else { + t.options.success(t.media, t.domNode, t); + } + } + }, + + handleError: function(e) { + var t = this; + + t.controls.hide(); + + // Tell user that the file cannot be played + if (t.options.error) { + t.options.error(e); + } + }, + + setPlayerSize: function(width,height) { + var t = this; + + if( !t.options.setDimensions ) { + return false; + } + + if (typeof width != 'undefined') { + t.width = width; + } + + if (typeof height != 'undefined') { + t.height = height; + } + + // detect 100% mode - use currentStyle for IE since css() doesn't return percentages + if (t.height.toString().indexOf('%') > 0 || t.$node.css('max-width') === '100%' || (t.$node[0].currentStyle && t.$node[0].currentStyle.maxWidth === '100%')) { + + // do we have the native dimensions yet? + var nativeWidth = (function() { + if (t.isVideo) { + if (t.media.videoWidth && t.media.videoWidth > 0) { + return t.media.videoWidth; + } else if (t.media.getAttribute('width') !== null) { + return t.media.getAttribute('width'); + } else { + return t.options.defaultVideoWidth; + } + } else { + return t.options.defaultAudioWidth; + } + })(); + + var nativeHeight = (function() { + if (t.isVideo) { + if (t.media.videoHeight && t.media.videoHeight > 0) { + return t.media.videoHeight; + } else if (t.media.getAttribute('height') !== null) { + return t.media.getAttribute('height'); + } else { + return t.options.defaultVideoHeight; + } + } else { + return t.options.defaultAudioHeight; + } + })(); + + var + parentWidth = t.container.parent().closest(':visible').width(), + parentHeight = t.container.parent().closest(':visible').height(), + newHeight = t.isVideo || !t.options.autosizeProgress ? parseInt(parentWidth * nativeHeight/nativeWidth, 10) : nativeHeight; + + // When we use percent, the newHeight can't be calculated so we get the container height + if(isNaN(newHeight) || ( parentHeight != 0 && newHeight > parentHeight )) { + newHeight = parentHeight; + } + + if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) { + parentWidth = $(window).width(); + newHeight = $(window).height(); + } + + if ( newHeight != 0 && parentWidth != 0 ) { + // set outer container size + t.container + .width(parentWidth) + .height(newHeight); + + // set native <video> or <audio> and shims + t.$media.add(t.container.find('.mejs-shim')) + .width('100%') + .height('100%'); + + // if shim is ready, send the size to the embeded plugin + if (t.isVideo) { + if (t.media.setVideoSize) { + t.media.setVideoSize(parentWidth, newHeight); + } + } + + // set the layers + t.layers.children('.mejs-layer') + .width('100%') + .height('100%'); + } + + + } else { + + t.container + .width(t.width) + .height(t.height); + + t.layers.children('.mejs-layer') + .width(t.width) + .height(t.height); + + } + + // special case for big play button so it doesn't go over the controls area + var playLayer = t.layers.find('.mejs-overlay-play'), + playButton = playLayer.find('.mejs-overlay-button'); + + playLayer.height(t.container.height() - t.controls.height()); + playButton.css('margin-top', '-' + (playButton.height()/2 - t.controls.height()/2).toString() + 'px' ); + + }, + + setControlsSize: function() { + var t = this, + usedWidth = 0, + railWidth = 0, + rail = t.controls.find('.mejs-time-rail'), + total = t.controls.find('.mejs-time-total'), + current = t.controls.find('.mejs-time-current'), + loaded = t.controls.find('.mejs-time-loaded'), + others = rail.siblings(), + lastControl = others.last(), + lastControlPosition = null; + + // skip calculation if hidden + if (!t.container.is(':visible') || !rail.length || !rail.is(':visible')) { + return; + } + + + // allow the size to come from custom CSS + if (t.options && !t.options.autosizeProgress) { + // Also, frontends devs can be more flexible + // due the opportunity of absolute positioning. + railWidth = parseInt(rail.css('width')); + } + + // attempt to autosize + if (railWidth === 0 || !railWidth) { + + // find the size of all the other controls besides the rail + others.each(function() { + var $this = $(this); + if ($this.css('position') != 'absolute' && $this.is(':visible')) { + usedWidth += $(this).outerWidth(true); + } + }); + + // fit the rail into the remaining space + railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width()); + } + + // resize the rail, + // but then check if the last control (say, the fullscreen button) got pushed down + // this often happens when zoomed + do { + // outer area + rail.width(railWidth); + // dark space + total.width(railWidth - (total.outerWidth(true) - total.width())); + + if (lastControl.css('position') != 'absolute') { + lastControlPosition = lastControl.position(); + railWidth--; + } + } while (lastControlPosition != null && lastControlPosition.top > 0 && railWidth > 0); + + if (t.setProgressRail) + t.setProgressRail(); + if (t.setCurrentRail) + t.setCurrentRail(); + }, + + + buildposter: function(player, controls, layers, media) { + var t = this, + poster = + $('<div class="mejs-poster mejs-layer">' + + '</div>') + .appendTo(layers), + posterUrl = player.$media.attr('poster'); + + // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) + if (player.options.poster !== '') { + posterUrl = player.options.poster; + } + + // second, try the real poster + if (posterUrl !== '' && posterUrl != null) { + t.setPoster(posterUrl); + } else { + poster.hide(); + } + + media.addEventListener('play',function() { + poster.hide(); + }, false); + + if(player.options.showPosterWhenEnded && player.options.autoRewind){ + media.addEventListener('ended',function() { + poster.show(); + }, false); + } + }, + + setPoster: function(url) { + var t = this, + posterDiv = t.container.find('.mejs-poster'), + posterImg = posterDiv.find('img'); + + if (posterImg.length == 0) { + posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv); + } + + posterImg.attr('src', url); + posterDiv.css({'background-image' : 'url(' + url + ')'}); + }, + + buildoverlays: function(player, controls, layers, media) { + var t = this; + if (!player.isVideo) + return; + + var + loading = + $('<div class="mejs-overlay mejs-layer">'+ + '<div class="mejs-overlay-loading"><span></span></div>'+ + '</div>') + .hide() // start out hidden + .appendTo(layers), + error = + $('<div class="mejs-overlay mejs-layer">'+ + '<div class="mejs-overlay-error"></div>'+ + '</div>') + .hide() // start out hidden + .appendTo(layers), + // this needs to come last so it's on top + bigPlay = + $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ + '<div class="mejs-overlay-button"></div>'+ + '</div>') + .appendTo(layers) + .bind('click', function() { // Removed 'touchstart' due issues on Samsung Android devices where a tap on bigPlay started and immediately stopped the video + if (t.options.clickToPlayPause) { + if (media.paused) { + media.play(); + } + } + }); + + /* + if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) { + bigPlay.remove(); + loading.remove(); + } + */ + + + // show/hide big play button + media.addEventListener('play',function() { + bigPlay.hide(); + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.hide(); + }, false); + + media.addEventListener('playing', function() { + bigPlay.hide(); + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.hide(); + }, false); + + media.addEventListener('seeking', function() { + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + + media.addEventListener('seeked', function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + }, false); + + media.addEventListener('pause',function() { + if (!mejs.MediaFeatures.isiPhone) { + bigPlay.show(); + } + }, false); + + media.addEventListener('waiting', function() { + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + + + // show/hide loading + media.addEventListener('loadeddata',function() { + // for some reason Chrome is firing this event + //if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') + // return; + + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + media.addEventListener('canplay',function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + }, false); + + // error handling + media.addEventListener('error',function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.show(); + error.find('mejs-overlay-error').html("Error loading this resource"); + }, false); + + media.addEventListener('keydown', function(e) { + t.onkeydown(player, media, e); + }, false); + }, + + buildkeyboard: function(player, controls, layers, media) { + + var t = this; + + // listen for key presses + t.globalBind('keydown', function(e) { + return t.onkeydown(player, media, e); + }); + + // check if someone clicked outside a player region, then kill its focus + t.globalBind('click', function(event) { + player.hasFocus = $(event.target).closest('.mejs-container').length != 0; + }); + + }, + onkeydown: function(player, media, e) { + if (player.hasFocus && player.options.enableKeyboard) { + // find a matching key + for (var i = 0, il = player.options.keyActions.length; i < il; i++) { + var keyAction = player.options.keyActions[i]; + + for (var j = 0, jl = keyAction.keys.length; j < jl; j++) { + if (e.keyCode == keyAction.keys[j]) { + if (typeof(e.preventDefault) == "function") e.preventDefault(); + keyAction.action(player, media, e.keyCode); + return false; + } + } + } + } + + return true; + }, + + findTracks: function() { + var t = this, + tracktags = t.$media.find('track'); + + // store for use by plugins + t.tracks = []; + tracktags.each(function(index, track) { + + track = $(track); + + t.tracks.push({ + srclang: (track.attr('srclang')) ? track.attr('srclang').toLowerCase() : '', + src: track.attr('src'), + kind: track.attr('kind'), + label: track.attr('label') || '', + entries: [], + isLoaded: false + }); + }); + }, + changeSkin: function(className) { + this.container[0].className = 'mejs-container ' + className; + this.setPlayerSize(this.width, this.height); + this.setControlsSize(); + }, + play: function() { + this.load(); + this.media.play(); + }, + pause: function() { + try { + this.media.pause(); + } catch (e) {} + }, + load: function() { + if (!this.isLoaded) { + this.media.load(); + } + + this.isLoaded = true; + }, + setMuted: function(muted) { + this.media.setMuted(muted); + }, + setCurrentTime: function(time) { + this.media.setCurrentTime(time); + }, + getCurrentTime: function() { + return this.media.currentTime; + }, + setVolume: function(volume) { + this.media.setVolume(volume); + }, + getVolume: function() { + return this.media.volume; + }, + setSrc: function(src) { + this.media.setSrc(src); + }, + remove: function() { + var t = this, featureIndex, feature; + + // invoke features cleanup + for (featureIndex in t.options.features) { + feature = t.options.features[featureIndex]; + if (t['clean' + feature]) { + try { + t['clean' + feature](t); + } catch (e) { + // TODO: report control error + //throw e; + // + // + } + } + } + + // grab video and put it back in place + if (!t.isDynamic) { + t.$media.prop('controls', true); + // detach events from the video + // TODO: detach event listeners better than this; + // also detach ONLY the events attached by this plugin! + t.$node.clone().insertBefore(t.container).show(); + t.$node.remove(); + } else { + t.$node.insertBefore(t.container); + } + + if (t.media.pluginType !== 'native') { + t.media.remove(); + } + + // Remove the player from the mejs.players object so that pauseOtherPlayers doesn't blow up when trying to pause a non existance flash api. + delete mejs.players[t.id]; + + if (typeof t.container == 'object') { + t.container.remove(); + } + t.globalUnbind(); + delete t.node.player; + } + }; + + (function(){ + var rwindow = /^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/; + + function splitEvents(events, id) { + // add player ID as an event namespace so it's easier to unbind them all later + var ret = {d: [], w: []}; + $.each((events || '').split(' '), function(k, v){ + var eventname = v + '.' + id; + if (eventname.indexOf('.') === 0) { + ret.d.push(eventname); + ret.w.push(eventname); + } + else { + ret[rwindow.test(v) ? 'w' : 'd'].push(eventname); + } + }); + ret.d = ret.d.join(' '); + ret.w = ret.w.join(' '); + return ret; + } + + mejs.MediaElementPlayer.prototype.globalBind = function(events, data, callback) { + var t = this; + events = splitEvents(events, t.id); + if (events.d) $(document).bind(events.d, data, callback); + if (events.w) $(window).bind(events.w, data, callback); + }; + + mejs.MediaElementPlayer.prototype.globalUnbind = function(events, callback) { + var t = this; + events = splitEvents(events, t.id); + if (events.d) $(document).unbind(events.d, callback); + if (events.w) $(window).unbind(events.w, callback); + }; + })(); + + // turn into jQuery plugin + if (typeof $ != 'undefined') { + $.fn.mediaelementplayer = function (options) { + if (options === false) { + this.each(function () { + var player = $(this).data('mediaelementplayer'); + if (player) { + player.remove(); + } + $(this).removeData('mediaelementplayer'); + }); + } + else { + this.each(function () { + $(this).data('mediaelementplayer', new mejs.MediaElementPlayer(this, options)); + }); + } + return this; + }; + + + $(document).ready(function() { + // auto enable using JSON attribute + $('.mejs-player').mediaelementplayer(); + }); + } + + // push out to window + window.MediaElementPlayer = mejs.MediaElementPlayer; + +})(mejs.$); + +(function($) { + + $.extend(mejs.MepDefaults, { + playpauseText: mejs.i18n.t('Play/Pause') + }); + + // PLAY/pause BUTTON + $.extend(MediaElementPlayer.prototype, { + buildplaypause: function(player, controls, layers, media) { + var + t = this, + play = + $('<div class="mejs-button mejs-playpause-button mejs-play" >' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.playpauseText + '" aria-label="' + t.options.playpauseText + '"></button>' + + '</div>') + .appendTo(controls) + .click(function(e) { + e.preventDefault(); + + if (media.paused) { + media.play(); + } else { + media.pause(); + } + + return false; + }); + + media.addEventListener('play',function() { + play.removeClass('mejs-play').addClass('mejs-pause'); + }, false); + media.addEventListener('playing',function() { + play.removeClass('mejs-play').addClass('mejs-pause'); + }, false); + + + media.addEventListener('pause',function() { + play.removeClass('mejs-pause').addClass('mejs-play'); + }, false); + media.addEventListener('paused',function() { + play.removeClass('mejs-pause').addClass('mejs-play'); + }, false); + } + }); + +})(mejs.$); + +(function($) { + + $.extend(mejs.MepDefaults, { + stopText: 'Stop' + }); + + // STOP BUTTON + $.extend(MediaElementPlayer.prototype, { + buildstop: function(player, controls, layers, media) { + var t = this, + stop = + $('<div class="mejs-button mejs-stop-button mejs-stop">' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '" aria-label="' + t.options.stopText + '"></button>' + + '</div>') + .appendTo(controls) + .click(function() { + if (!media.paused) { + media.pause(); + } + if (media.currentTime > 0) { + media.setCurrentTime(0); + media.pause(); + controls.find('.mejs-time-current').width('0px'); + controls.find('.mejs-time-handle').css('left', '0px'); + controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); + controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); + layers.find('.mejs-poster').show(); + } + }); + } + }); + +})(mejs.$); + +(function($) { + // progress/loaded bar + $.extend(MediaElementPlayer.prototype, { + buildprogress: function(player, controls, layers, media) { + + $('<div class="mejs-time-rail">'+ + '<span class="mejs-time-total">'+ + '<span class="mejs-time-buffering"></span>'+ + '<span class="mejs-time-loaded"></span>'+ + '<span class="mejs-time-current"></span>'+ + '<span class="mejs-time-handle"></span>'+ + '<span class="mejs-time-float">' + + '<span class="mejs-time-float-current">00:00</span>' + + '<span class="mejs-time-float-corner"></span>' + + '</span>'+ + '</span>'+ + '</div>') + .appendTo(controls); + controls.find('.mejs-time-buffering').hide(); + + var + t = this, + total = controls.find('.mejs-time-total'), + loaded = controls.find('.mejs-time-loaded'), + current = controls.find('.mejs-time-current'), + handle = controls.find('.mejs-time-handle'), + timefloat = controls.find('.mejs-time-float'), + timefloatcurrent = controls.find('.mejs-time-float-current'), + handleMouseMove = function (e) { + // mouse or touch position relative to the object + if (e.originalEvent.changedTouches) { + var x = e.originalEvent.changedTouches[0].pageX; + }else{ + var x = e.pageX; + } + + var offset = total.offset(), + width = total.outerWidth(true), + percentage = 0, + newTime = 0, + pos = 0; + + + if (media.duration) { + if (x < offset.left) { + x = offset.left; + } else if (x > width + offset.left) { + x = width + offset.left; + } + + pos = x - offset.left; + percentage = (pos / width); + newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; + + // seek to where the mouse is + if (mouseIsDown && newTime !== media.currentTime) { + media.setCurrentTime(newTime); + } + + // position floating time box + if (!mejs.MediaFeatures.hasTouch) { + timefloat.css('left', pos); + timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); + timefloat.show(); + } + } + }, + mouseIsDown = false, + mouseIsOver = false; + + // handle clicks + //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); + total + .bind('mousedown touchstart', function (e) { + // only handle left clicks or touch + if (e.which === 1 || e.which === 0) { + mouseIsDown = true; + handleMouseMove(e); + t.globalBind('mousemove.dur touchmove.dur', function(e) { + handleMouseMove(e); + }); + t.globalBind('mouseup.dur touchend.dur', function (e) { + mouseIsDown = false; + timefloat.hide(); + t.globalUnbind('.dur'); + }); + return false; + } + }) + .bind('mouseenter', function(e) { + mouseIsOver = true; + t.globalBind('mousemove.dur', function(e) { + handleMouseMove(e); + }); + if (!mejs.MediaFeatures.hasTouch) { + timefloat.show(); + } + }) + .bind('mouseleave',function(e) { + mouseIsOver = false; + if (!mouseIsDown) { + t.globalUnbind('.dur'); + timefloat.hide(); + } + }); + + // loading + media.addEventListener('progress', function (e) { + player.setProgressRail(e); + player.setCurrentRail(e); + }, false); + + // current time + media.addEventListener('timeupdate', function(e) { + player.setProgressRail(e); + player.setCurrentRail(e); + }, false); + + + // store for later use + t.loaded = loaded; + t.total = total; + t.current = current; + t.handle = handle; + }, + setProgressRail: function(e) { + + var + t = this, + target = (e != undefined) ? e.target : t.media, + percent = null; + + // newest HTML5 spec has buffered array (FF4, Webkit) + if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { + // TODO: account for a real array with multiple values (only Firefox 4 has this so far) + percent = target.buffered.end(0) / target.duration; + } + // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() + // to be anything other than 0. If the byte count is available we use this instead. + // Browsers that support the else if do not seem to have the bufferedBytes value and + // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. + else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) { + percent = target.bufferedBytes / target.bytesTotal; + } + // Firefox 3 with an Ogg file seems to go this way + else if (e && e.lengthComputable && e.total != 0) { + percent = e.loaded/e.total; + } + + // finally update the progress bar + if (percent !== null) { + percent = Math.min(1, Math.max(0, percent)); + // update loaded bar + if (t.loaded && t.total) { + t.loaded.width(t.total.width() * percent); + } + } + }, + setCurrentRail: function() { + + var t = this; + + if (t.media.currentTime != undefined && t.media.duration) { + + // update bar and handle + if (t.total && t.handle) { + var + newWidth = Math.round(t.total.width() * t.media.currentTime / t.media.duration), + handlePos = newWidth - Math.round(t.handle.outerWidth(true) / 2); + + t.current.width(newWidth); + t.handle.css('left', handlePos); + } + } + + } + }); +})(mejs.$); + +(function($) { + + // options + $.extend(mejs.MepDefaults, { + duration: -1, + timeAndDurationSeparator: '<span> | </span>' + }); + + + // current and duration 00:00 / 00:00 + $.extend(MediaElementPlayer.prototype, { + buildcurrent: function(player, controls, layers, media) { + var t = this; + + $('<div class="mejs-time">'+ + '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') + + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')+ '</span>'+ + '</div>') + .appendTo(controls); + + t.currenttime = t.controls.find('.mejs-currenttime'); + + media.addEventListener('timeupdate',function() { + player.updateCurrent(); + }, false); + }, + + + buildduration: function(player, controls, layers, media) { + var t = this; + + if (controls.children().last().find('.mejs-currenttime').length > 0) { + $(t.options.timeAndDurationSeparator + + '<span class="mejs-duration">' + + (t.options.duration > 0 ? + mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : + ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) + ) + + '</span>') + .appendTo(controls.find('.mejs-time')); + } else { + + // add class to current time + controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); + + $('<div class="mejs-time mejs-duration-container">'+ + '<span class="mejs-duration">' + + (t.options.duration > 0 ? + mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : + ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) + ) + + '</span>' + + '</div>') + .appendTo(controls); + } + + t.durationD = t.controls.find('.mejs-duration'); + + media.addEventListener('timeupdate',function() { + player.updateDuration(); + }, false); + }, + + updateCurrent: function() { + var t = this; + + if (t.currenttime) { + t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); + } + }, + + updateDuration: function() { + var t = this; + + //Toggle the long video class if the video is longer than an hour. + t.container.toggleClass("mejs-long-video", t.media.duration > 3600); + + if (t.durationD && (t.options.duration > 0 || t.media.duration)) { + t.durationD.html(mejs.Utility.secondsToTimeCode(t.options.duration > 0 ? t.options.duration : t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); + } + } + }); + +})(mejs.$); + +(function($) { + + $.extend(mejs.MepDefaults, { + muteText: mejs.i18n.t('Mute Toggle'), + hideVolumeOnTouchDevices: true, + + audioVolume: 'horizontal', + videoVolume: 'vertical' + }); + + $.extend(MediaElementPlayer.prototype, { + buildvolume: function(player, controls, layers, media) { + + // Android and iOS don't support volume controls + if ((mejs.MediaFeatures.isAndroid || mejs.MediaFeatures.isiOS) && this.options.hideVolumeOnTouchDevices) + return; + + var t = this, + mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume, + mute = (mode == 'horizontal') ? + + // horizontal version + $('<div class="mejs-button mejs-volume-button mejs-mute">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+ + '</div>' + + '<div class="mejs-horizontal-volume-slider">'+ // outer background + '<div class="mejs-horizontal-volume-total"></div>'+ // line background + '<div class="mejs-horizontal-volume-current"></div>'+ // current volume + '<div class="mejs-horizontal-volume-handle"></div>'+ // handle + '</div>' + ) + .appendTo(controls) : + + // vertical version + $('<div class="mejs-button mejs-volume-button mejs-mute">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+ + '<div class="mejs-volume-slider">'+ // outer background + '<div class="mejs-volume-total"></div>'+ // line background + '<div class="mejs-volume-current"></div>'+ // current volume + '<div class="mejs-volume-handle"></div>'+ // handle + '</div>'+ + '</div>') + .appendTo(controls), + volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'), + volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'), + volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'), + volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'), + + positionVolumeHandle = function(volume, secondTry) { + + if (!volumeSlider.is(':visible') && typeof secondTry == 'undefined') { + volumeSlider.show(); + positionVolumeHandle(volume, true); + volumeSlider.hide() + return; + } + + // correct to 0-1 + volume = Math.max(0,volume); + volume = Math.min(volume,1); + + // ajust mute button style + if (volume == 0) { + mute.removeClass('mejs-mute').addClass('mejs-unmute'); + } else { + mute.removeClass('mejs-unmute').addClass('mejs-mute'); + } + + // position slider + if (mode == 'vertical') { + var + + // height of the full size volume slider background + totalHeight = volumeTotal.height(), + + // top/left of full size volume slider background + totalPosition = volumeTotal.position(), + + // the new top position based on the current volume + // 70% volume on 100px height == top:30px + newTop = totalHeight - (totalHeight * volume); + + // handle + volumeHandle.css('top', Math.round(totalPosition.top + newTop - (volumeHandle.height() / 2))); + + // show the current visibility + volumeCurrent.height(totalHeight - newTop ); + volumeCurrent.css('top', totalPosition.top + newTop); + } else { + var + + // height of the full size volume slider background + totalWidth = volumeTotal.width(), + + // top/left of full size volume slider background + totalPosition = volumeTotal.position(), + + // the new left position based on the current volume + newLeft = totalWidth * volume; + + // handle + volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2))); + + // rezize the current part of the volume bar + volumeCurrent.width( Math.round(newLeft) ); + } + }, + handleVolumeMove = function(e) { + + var volume = null, + totalOffset = volumeTotal.offset(); + + // calculate the new volume based on the moust position + if (mode == 'vertical') { + + var + railHeight = volumeTotal.height(), + totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), + newY = e.pageY - totalOffset.top; + + volume = (railHeight - newY) / railHeight; + + // the controls just hide themselves (usually when mouse moves too far up) + if (totalOffset.top == 0 || totalOffset.left == 0) + return; + + } else { + var + railWidth = volumeTotal.width(), + newX = e.pageX - totalOffset.left; + + volume = newX / railWidth; + } + + // ensure the volume isn't outside 0-1 + volume = Math.max(0,volume); + volume = Math.min(volume,1); + + // position the slider and handle + positionVolumeHandle(volume); + + // set the media object (this will trigger the volumechanged event) + if (volume == 0) { + media.setMuted(true); + } else { + media.setMuted(false); + } + media.setVolume(volume); + }, + mouseIsDown = false, + mouseIsOver = false; + + // SLIDER + + mute + .hover(function() { + volumeSlider.show(); + mouseIsOver = true; + }, function() { + mouseIsOver = false; + + if (!mouseIsDown && mode == 'vertical') { + volumeSlider.hide(); + } + }); + + volumeSlider + .bind('mouseover', function() { + mouseIsOver = true; + }) + .bind('mousedown', function (e) { + handleVolumeMove(e); + t.globalBind('mousemove.vol', function(e) { + handleVolumeMove(e); + }); + t.globalBind('mouseup.vol', function () { + mouseIsDown = false; + t.globalUnbind('.vol'); + + if (!mouseIsOver && mode == 'vertical') { + volumeSlider.hide(); + } + }); + mouseIsDown = true; + + return false; + }); + + + // MUTE button + mute.find('button').click(function() { + media.setMuted( !media.muted ); + }); + + // listen for volume change events from other sources + media.addEventListener('volumechange', function(e) { + if (!mouseIsDown) { + if (media.muted) { + positionVolumeHandle(0); + mute.removeClass('mejs-mute').addClass('mejs-unmute'); + } else { + positionVolumeHandle(media.volume); + mute.removeClass('mejs-unmute').addClass('mejs-mute'); + } + } + }, false); + + if (t.container.is(':visible')) { + // set initial volume + positionVolumeHandle(player.options.startVolume); + + // mutes the media and sets the volume icon muted if the initial volume is set to 0 + if (player.options.startVolume === 0) { + media.setMuted(true); + } + + // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements + if (media.pluginType === 'native') { + media.setVolume(player.options.startVolume); + } + } + } + }); + +})(mejs.$); + +(function($) { + + $.extend(mejs.MepDefaults, { + usePluginFullScreen: true, + newWindowCallback: function() { return '';}, + fullscreenText: mejs.i18n.t('Fullscreen') + }); + + $.extend(MediaElementPlayer.prototype, { + + isFullScreen: false, + + isNativeFullScreen: false, + + isInIframe: false, + + buildfullscreen: function(player, controls, layers, media) { + + if (!player.isVideo) + return; + + player.isInIframe = (window.location != window.parent.location); + + // native events + if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + + // chrome doesn't alays fire this in an iframe + var func = function(e) { + if (player.isFullScreen) { + if (mejs.MediaFeatures.isFullScreen()) { + player.isNativeFullScreen = true; + // reset the controls once we are fully in full screen + player.setControlsSize(); + } else { + player.isNativeFullScreen = false; + // when a user presses ESC + // make sure to put the player back into place + player.exitFullScreen(); + } + } + }; + + player.globalBind(mejs.MediaFeatures.fullScreenEventName, func); + } + + var t = this, + normalHeight = 0, + normalWidth = 0, + container = player.container, + fullscreenBtn = + $('<div class="mejs-button mejs-fullscreen-button">' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '" aria-label="' + t.options.fullscreenText + '"></button>' + + '</div>') + .appendTo(controls); + + if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) { + + fullscreenBtn.click(function() { + var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen; + + if (isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + }); + + } else { + + var hideTimeout = null, + supportsPointerEvents = (function() { + // TAKEN FROM MODERNIZR + var element = document.createElement('x'), + documentElement = document.documentElement, + getComputedStyle = window.getComputedStyle, + supports; + if(!('pointerEvents' in element.style)){ + return false; + } + element.style.pointerEvents = 'auto'; + element.style.pointerEvents = 'x'; + documentElement.appendChild(element); + supports = getComputedStyle && + getComputedStyle(element, '').pointerEvents === 'auto'; + documentElement.removeChild(element); + return !!supports; + })(); + + // + + if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :( + + // allows clicking through the fullscreen button and controls down directly to Flash + + /* + When a user puts his mouse over the fullscreen button, the controls are disabled + So we put a div over the video and another one on iether side of the fullscreen button + that caputre mouse movement + and restore the controls once the mouse moves outside of the fullscreen button + */ + + var fullscreenIsDisabled = false, + restoreControls = function() { + if (fullscreenIsDisabled) { + // hide the hovers + for (var i in hoverDivs) { + hoverDivs[i].hide(); + } + + // restore the control bar + fullscreenBtn.css('pointer-events', ''); + t.controls.css('pointer-events', ''); + + // prevent clicks from pausing video + t.media.removeEventListener('click', t.clickToPlayPauseCallback); + + // store for later + fullscreenIsDisabled = false; + } + }, + hoverDivs = {}, + hoverDivNames = ['top', 'left', 'right', 'bottom'], + i, len, + positionHoverDivs = function() { + var fullScreenBtnOffsetLeft = fullscreenBtn.offset().left - t.container.offset().left, + fullScreenBtnOffsetTop = fullscreenBtn.offset().top - t.container.offset().top, + fullScreenBtnWidth = fullscreenBtn.outerWidth(true), + fullScreenBtnHeight = fullscreenBtn.outerHeight(true), + containerWidth = t.container.width(), + containerHeight = t.container.height(); + + for (i in hoverDivs) { + hoverDivs[i].css({position: 'absolute', top: 0, left: 0}); //, backgroundColor: '#f00'}); + } + + // over video, but not controls + hoverDivs['top'] + .width( containerWidth ) + .height( fullScreenBtnOffsetTop ); + + // over controls, but not the fullscreen button + hoverDivs['left'] + .width( fullScreenBtnOffsetLeft ) + .height( fullScreenBtnHeight ) + .css({top: fullScreenBtnOffsetTop}); + + // after the fullscreen button + hoverDivs['right'] + .width( containerWidth - fullScreenBtnOffsetLeft - fullScreenBtnWidth ) + .height( fullScreenBtnHeight ) + .css({top: fullScreenBtnOffsetTop, + left: fullScreenBtnOffsetLeft + fullScreenBtnWidth}); + + // under the fullscreen button + hoverDivs['bottom'] + .width( containerWidth ) + .height( containerHeight - fullScreenBtnHeight - fullScreenBtnOffsetTop ) + .css({top: fullScreenBtnOffsetTop + fullScreenBtnHeight}); + }; + + t.globalBind('resize', function() { + positionHoverDivs(); + }); + + for (i = 0, len = hoverDivNames.length; i < len; i++) { + hoverDivs[hoverDivNames[i]] = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls).hide(); + } + + // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash + fullscreenBtn.on('mouseover',function() { + + if (!t.isFullScreen) { + + var buttonPos = fullscreenBtn.offset(), + containerPos = player.container.offset(); + + // move the button in Flash into place + media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false); + + // allows click through + fullscreenBtn.css('pointer-events', 'none'); + t.controls.css('pointer-events', 'none'); + + // restore click-to-play + t.media.addEventListener('click', t.clickToPlayPauseCallback); + + // show the divs that will restore things + for (i in hoverDivs) { + hoverDivs[i].show(); + } + + positionHoverDivs(); + + fullscreenIsDisabled = true; + } + + }); + + // restore controls anytime the user enters or leaves fullscreen + media.addEventListener('fullscreenchange', function(e) { + t.isFullScreen = !t.isFullScreen; + // don't allow plugin click to pause video - messes with + // plugin's controls + if (t.isFullScreen) { + t.media.removeEventListener('click', t.clickToPlayPauseCallback); + } else { + t.media.addEventListener('click', t.clickToPlayPauseCallback); + } + restoreControls(); + }); + + + // the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events + // so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button + + t.globalBind('mousemove', function(e) { + + // if the mouse is anywhere but the fullsceen button, then restore it all + if (fullscreenIsDisabled) { + + var fullscreenBtnPos = fullscreenBtn.offset(); + + + if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) || + e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true) + ) { + + fullscreenBtn.css('pointer-events', ''); + t.controls.css('pointer-events', ''); + + fullscreenIsDisabled = false; + } + } + }); + + + + } else { + + // the hover state will show the fullscreen button in Flash to hover up and click + + fullscreenBtn + .on('mouseover', function() { + + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + delete hideTimeout; + } + + var buttonPos = fullscreenBtn.offset(), + containerPos = player.container.offset(); + + media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true); + + }) + .on('mouseout', function() { + + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + delete hideTimeout; + } + + hideTimeout = setTimeout(function() { + media.hideFullscreenButton(); + }, 1500); + + + }); + } + } + + player.fullscreenBtn = fullscreenBtn; + + t.globalBind('keydown',function (e) { + if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) { + player.exitFullScreen(); + } + }); + + }, + + cleanfullscreen: function(player) { + player.exitFullScreen(); + }, + + containerSizeTimeout: null, + + enterFullScreen: function() { + + var t = this; + + // firefox+flash can't adjust plugin sizes without resetting :( + if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) { + //t.media.setFullscreen(true); + //player.isFullScreen = true; + return; + } + + // set it to not show scroll bars so 100% will work + $(document.documentElement).addClass('mejs-fullscreen'); + + // store sizing + normalHeight = t.container.height(); + normalWidth = t.container.width(); + + // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now) + if (t.media.pluginType === 'native') { + if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + + mejs.MediaFeatures.requestFullScreen(t.container[0]); + //return; + + if (t.isInIframe) { + // sometimes exiting from fullscreen doesn't work + // notably in Chrome <iframe>. Fixed in version 17 + setTimeout(function checkFullscreen() { + + if (t.isNativeFullScreen) { + var zoomMultiplier = window["devicePixelRatio"] || 1; + // Use a percent error margin since devicePixelRatio is a float and not exact. + var percentErrorMargin = 0.002; // 0.2% + var windowWidth = zoomMultiplier * $(window).width(); + var screenWidth = screen.width; + var absDiff = Math.abs(screenWidth - windowWidth); + var marginError = screenWidth * percentErrorMargin; + + // check if the video is suddenly not really fullscreen + if (absDiff > marginError) { + // manually exit + t.exitFullScreen(); + } else { + // test again + setTimeout(checkFullscreen, 500); + } + } + + + }, 500); + } + + } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) { + t.media.webkitEnterFullscreen(); + return; + } + } + + // check for iframe launch + if (t.isInIframe) { + var url = t.options.newWindowCallback(this); + + + if (url !== '') { + + // launch immediately + if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.pause(); + window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); + return; + } else { + setTimeout(function() { + if (!t.isNativeFullScreen) { + t.pause(); + window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); + } + }, 250); + } + } + + } + + // full window code + + + + // make full size + t.container + .addClass('mejs-container-fullscreen') + .width('100%') + .height('100%'); + //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); + + // Only needed for safari 5.1 native full screen, can cause display issues elsewhere + // Actually, it seems to be needed for IE8, too + //if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.containerSizeTimeout = setTimeout(function() { + t.container.css({width: '100%', height: '100%'}); + t.setControlsSize(); + }, 500); + //} + + if (t.media.pluginType === 'native') { + t.$media + .width('100%') + .height('100%'); + } else { + t.container.find('.mejs-shim') + .width('100%') + .height('100%'); + + //if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.media.setVideoSize($(window).width(),$(window).height()); + //} + } + + t.layers.children('div') + .width('100%') + .height('100%'); + + if (t.fullscreenBtn) { + t.fullscreenBtn + .removeClass('mejs-fullscreen') + .addClass('mejs-unfullscreen'); + } + + t.setControlsSize(); + t.isFullScreen = true; + + t.container.find('.mejs-captions-text').css('font-size', screen.width / t.width * 1.00 * 100 + '%'); + t.container.find('.mejs-captions-position').css('bottom', '45px'); + }, + + exitFullScreen: function() { + + var t = this; + + // Prevent container from attempting to stretch a second time + clearTimeout(t.containerSizeTimeout); + + // firefox can't adjust plugins + if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { + t.media.setFullscreen(false); + //player.isFullScreen = false; + return; + } + + // come outo of native fullscreen + if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) { + mejs.MediaFeatures.cancelFullScreen(); + } + + // restore scroll bars to document + $(document.documentElement).removeClass('mejs-fullscreen'); + + t.container + .removeClass('mejs-container-fullscreen') + .width(normalWidth) + .height(normalHeight); + //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); + + if (t.media.pluginType === 'native') { + t.$media + .width(normalWidth) + .height(normalHeight); + } else { + t.container.find('.mejs-shim') + .width(normalWidth) + .height(normalHeight); + + t.media.setVideoSize(normalWidth, normalHeight); + } + + t.layers.children('div') + .width(normalWidth) + .height(normalHeight); + + t.fullscreenBtn + .removeClass('mejs-unfullscreen') + .addClass('mejs-fullscreen'); + + t.setControlsSize(); + t.isFullScreen = false; + + t.container.find('.mejs-captions-text').css('font-size',''); + t.container.find('.mejs-captions-position').css('bottom', ''); + } + }); + +})(mejs.$); + +(function($) { + + // Speed + $.extend(mejs.MepDefaults, { + + speeds: ['1.50', '1.25', '1.00', '0.75'], + + defaultSpeed: '1.00' + + }); + + $.extend(MediaElementPlayer.prototype, { + + buildspeed: function(player, controls, layers, media) { + var t = this; + + if (t.media.pluginType == 'native') { + var s = '<div class="mejs-button mejs-speed-button"><button type="button">'+t.options.defaultSpeed+'x</button><div class="mejs-speed-selector"><ul>'; + var i, ss; + + if ($.inArray(t.options.defaultSpeed, t.options.speeds) === -1) { + t.options.speeds.push(t.options.defaultSpeed); + } + + t.options.speeds.sort(function(a, b) { + return parseFloat(b) - parseFloat(a); + }); + + for (i = 0; i < t.options.speeds.length; i++) { + s += '<li><input type="radio" name="speed" value="' + t.options.speeds[i] + '" id="' + t.options.speeds[i] + '" '; + if (t.options.speeds[i] == t.options.defaultSpeed) { + s += 'checked=true '; + s += '/><label for="' + t.options.speeds[i] + '" class="mejs-speed-selected">'+ t.options.speeds[i] + 'x</label></li>'; + } else { + s += '/><label for="' + t.options.speeds[i] + '">'+ t.options.speeds[i] + 'x</label></li>'; + } + } + s += '</ul></div></div>'; + + player.speedButton = $(s).appendTo(controls); + + player.playbackspeed = t.options.defaultSpeed; + + player.speedButton + .on('click', 'input[type=radio]', function() { + player.playbackspeed = $(this).attr('value'); + media.playbackRate = parseFloat(player.playbackspeed); + player.speedButton.find('button').text(player.playbackspeed + 'x'); + player.speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected'); + player.speedButton.find('input[type=radio]:checked').next().addClass('mejs-speed-selected'); + }); + + ss = player.speedButton.find('.mejs-speed-selector'); + ss.height(this.speedButton.find('.mejs-speed-selector ul').outerHeight(true) + player.speedButton.find('.mejs-speed-translations').outerHeight(true)); + ss.css('top', (-1 * ss.height()) + 'px'); + } + } + }); + +})(mejs.$); + +(function($) { + + // add extra default options + $.extend(mejs.MepDefaults, { + // this will automatically turn on a <track> + startLanguage: '', + + tracksText: mejs.i18n.t('Captions/Subtitles'), + + // option to remove the [cc] button when no <track kind="subtitles"> are present + hideCaptionsButtonWhenEmpty: true, + + // If true and we only have one track, change captions to popup + toggleCaptionsButtonWhenOnlyOne: false, + + // #id or .class + slidesSelector: '' + }); + + $.extend(MediaElementPlayer.prototype, { + + hasChapters: false, + + buildtracks: function(player, controls, layers, media) { + if (player.tracks.length === 0) + return; + + var t = this, + i, + options = ''; + + if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide + for (i = t.domNode.textTracks.length - 1; i >= 0; i--) { + t.domNode.textTracks[i].mode = "hidden"; + } + } + player.chapters = + $('<div class="mejs-chapters mejs-layer"></div>') + .prependTo(layers).hide(); + player.captions = + $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover"><span class="mejs-captions-text"></span></div></div>') + .prependTo(layers).hide(); + player.captionsText = player.captions.find('.mejs-captions-text'); + player.captionsButton = + $('<div class="mejs-button mejs-captions-button">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '" aria-label="' + t.options.tracksText + '"></button>'+ + '<div class="mejs-captions-selector">'+ + '<ul>'+ + '<li>'+ + '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' + + '<label for="' + player.id + '_captions_none">' + mejs.i18n.t('None') +'</label>'+ + '</li>' + + '</ul>'+ + '</div>'+ + '</div>') + .appendTo(controls); + + + var subtitleCount = 0; + for (i=0; i<player.tracks.length; i++) { + if (player.tracks[i].kind == 'subtitles') { + subtitleCount++; + } + } + + // if only one language then just make the button a toggle + if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitleCount == 1){ + // click + player.captionsButton.on('click',function() { + if (player.selectedTrack === null) { + lang = player.tracks[0].srclang; + } else { + lang = 'none'; + } + player.setTrack(lang); + }); + } else { + // hover or keyboard focus + player.captionsButton.on( 'mouseenter focusin', function() { + $(this).find('.mejs-captions-selector').css('visibility','visible'); + }) + + // handle clicks to the language radio buttons + .on('click','input[type=radio]',function() { + lang = this.value; + player.setTrack(lang); + }); + + player.captionsButton.on( 'mouseleave focusout', function() { + $(this).find(".mejs-captions-selector").css("visibility","hidden"); + }); + + } + + if (!player.options.alwaysShowControls) { + // move with controls + player.container + .bind('controlsshown', function () { + // push captions above controls + player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); + + }) + .bind('controlshidden', function () { + if (!media.paused) { + // move back to normal place + player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); + } + }); + } else { + player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); + } + + player.trackToLoad = -1; + player.selectedTrack = null; + player.isLoadingTrack = false; + + // add to list + for (i=0; i<player.tracks.length; i++) { + if (player.tracks[i].kind == 'subtitles') { + player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label); + } + } + + // start loading tracks + player.loadNextTrack(); + + media.addEventListener('timeupdate',function(e) { + player.displayCaptions(); + }, false); + + if (player.options.slidesSelector !== '') { + player.slidesContainer = $(player.options.slidesSelector); + + media.addEventListener('timeupdate',function(e) { + player.displaySlides(); + }, false); + + } + + media.addEventListener('loadedmetadata', function(e) { + player.displayChapters(); + }, false); + + player.container.hover( + function () { + // chapters + if (player.hasChapters) { + player.chapters.css('visibility','visible'); + player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight()); + } + }, + function () { + if (player.hasChapters && !media.paused) { + player.chapters.fadeOut(200, function() { + $(this).css('visibility','hidden'); + $(this).css('display','block'); + }); + } + }); + + // check for autoplay + if (player.node.getAttribute('autoplay') !== null) { + player.chapters.css('visibility','hidden'); + } + }, + + setTrack: function(lang){ + + var t = this, + i; + + if (lang == 'none') { + t.selectedTrack = null; + t.captionsButton.removeClass('mejs-captions-enabled'); + } else { + for (i=0; i<t.tracks.length; i++) { + if (t.tracks[i].srclang == lang) { + if (t.selectedTrack === null) + t.captionsButton.addClass('mejs-captions-enabled'); + t.selectedTrack = t.tracks[i]; + t.captions.attr('lang', t.selectedTrack.srclang); + t.displayCaptions(); + break; + } + } + } + }, + + loadNextTrack: function() { + var t = this; + + t.trackToLoad++; + if (t.trackToLoad < t.tracks.length) { + t.isLoadingTrack = true; + t.loadTrack(t.trackToLoad); + } else { + // add done? + t.isLoadingTrack = false; + + t.checkForTracks(); + } + }, + + loadTrack: function(index){ + var + t = this, + track = t.tracks[index], + after = function() { + + track.isLoaded = true; + + // create button + //t.addTrackButton(track.srclang); + t.enableTrackButton(track.srclang, track.label); + + t.loadNextTrack(); + + }; + + + $.ajax({ + url: track.src, + dataType: "text", + success: function(d) { + + // parse the loaded file + if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) { + track.entries = mejs.TrackFormatParser.dfxp.parse(d); + } else { + track.entries = mejs.TrackFormatParser.webvtt.parse(d); + } + + after(); + + if (track.kind == 'chapters') { + t.media.addEventListener('play', function(e) { + if (t.media.duration > 0) { + t.displayChapters(track); + } + }, false); + } + + if (track.kind == 'slides') { + t.setupSlides(track); + } + }, + error: function() { + t.loadNextTrack(); + } + }); + }, + + enableTrackButton: function(lang, label) { + var t = this; + + if (label === '') { + label = mejs.language.codes[lang] || lang; + } + + t.captionsButton + .find('input[value=' + lang + ']') + .prop('disabled',false) + .siblings('label') + .html( label ); + + // auto select + if (t.options.startLanguage == lang) { + $('#' + t.id + '_captions_' + lang).prop('checked', true).trigger('click'); + } + + t.adjustLanguageBox(); + }, + + addTrackButton: function(lang, label) { + var t = this; + if (label === '') { + label = mejs.language.codes[lang] || lang; + } + + t.captionsButton.find('ul').append( + $('<li>'+ + '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' + + '<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+ + '</li>') + ); + + t.adjustLanguageBox(); + + // remove this from the dropdownlist (if it exists) + t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove(); + }, + + adjustLanguageBox:function() { + var t = this; + // adjust the size of the outer box + t.captionsButton.find('.mejs-captions-selector').height( + t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) + + t.captionsButton.find('.mejs-captions-translations').outerHeight(true) + ); + }, + + checkForTracks: function() { + var + t = this, + hasSubtitles = false; + + // check if any subtitles + if (t.options.hideCaptionsButtonWhenEmpty) { + for (i=0; i<t.tracks.length; i++) { + if (t.tracks[i].kind == 'subtitles') { + hasSubtitles = true; + break; + } + } + + if (!hasSubtitles) { + t.captionsButton.hide(); + t.setControlsSize(); + } + } + }, + + displayCaptions: function() { + + if (typeof this.tracks == 'undefined') + return; + + var + t = this, + i, + track = t.selectedTrack; + + if (track !== null && track.isLoaded) { + for (i=0; i<track.entries.times.length; i++) { + if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop) { + // Set the line before the timecode as a class so the cue can be targeted if needed + t.captionsText.html(track.entries.text[i]).attr('class', 'mejs-captions-text ' + (track.entries.times[i].identifier || '')); + t.captions.show().height(0); + return; // exit out if one is visible; + } + } + t.captions.hide(); + } else { + t.captions.hide(); + } + }, + + setupSlides: function(track) { + var t = this; + + t.slides = track; + t.slides.entries.imgs = [t.slides.entries.text.length]; + t.showSlide(0); + + }, + + showSlide: function(index) { + if (typeof this.tracks == 'undefined' || typeof this.slidesContainer == 'undefined') { + return; + } + + var t = this, + url = t.slides.entries.text[index], + img = t.slides.entries.imgs[index]; + + if (typeof img == 'undefined' || typeof img.fadeIn == 'undefined') { + + t.slides.entries.imgs[index] = img = $('<img src="' + url + '">') + .on('load', function() { + img.appendTo(t.slidesContainer) + .hide() + .fadeIn() + .siblings(':visible') + .fadeOut(); + + }); + + } else { + + if (!img.is(':visible') && !img.is(':animated')) { + + // + + img.fadeIn() + .siblings(':visible') + .fadeOut(); + } + } + + }, + + displaySlides: function() { + + if (typeof this.slides == 'undefined') + return; + + var + t = this, + slides = t.slides, + i; + + for (i=0; i<slides.entries.times.length; i++) { + if (t.media.currentTime >= slides.entries.times[i].start && t.media.currentTime <= slides.entries.times[i].stop){ + + t.showSlide(i); + + return; // exit out if one is visible; + } + } + }, + + displayChapters: function() { + var + t = this, + i; + + for (i=0; i<t.tracks.length; i++) { + if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) { + t.drawChapters(t.tracks[i]); + t.hasChapters = true; + break; + } + } + }, + + drawChapters: function(chapters) { + var + t = this, + i, + dur, + //width, + //left, + percent = 0, + usedPercent = 0; + + t.chapters.empty(); + + for (i=0; i<chapters.entries.times.length; i++) { + dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; + percent = Math.floor(dur / t.media.duration * 100); + if (percent + usedPercent > 100 || // too large + i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in + { + percent = 100 - usedPercent; + } + //width = Math.floor(t.width * dur / t.media.duration); + //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); + //if (left + width > t.width) { + // width = t.width - left; + //} + + t.chapters.append( $( + '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + + '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + + '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + + '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + + '</div>' + + '</div>')); + usedPercent += percent; + } + + t.chapters.find('div.mejs-chapter').click(function() { + t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); + if (t.media.paused) { + t.media.play(); + } + }); + + t.chapters.show(); + } + }); + + + + mejs.language = { + codes: { + af:'Afrikaans', + sq:'Albanian', + ar:'Arabic', + be:'Belarusian', + bg:'Bulgarian', + ca:'Catalan', + zh:'Chinese', + 'zh-cn':'Chinese Simplified', + 'zh-tw':'Chinese Traditional', + hr:'Croatian', + cs:'Czech', + da:'Danish', + nl:'Dutch', + en:'English', + et:'Estonian', + fl:'Filipino', + fi:'Finnish', + fr:'French', + gl:'Galician', + de:'German', + el:'Greek', + ht:'Haitian Creole', + iw:'Hebrew', + hi:'Hindi', + hu:'Hungarian', + is:'Icelandic', + id:'Indonesian', + ga:'Irish', + it:'Italian', + ja:'Japanese', + ko:'Korean', + lv:'Latvian', + lt:'Lithuanian', + mk:'Macedonian', + ms:'Malay', + mt:'Maltese', + no:'Norwegian', + fa:'Persian', + pl:'Polish', + pt:'Portuguese', + // 'pt-pt':'Portuguese (Portugal)', + ro:'Romanian', + ru:'Russian', + sr:'Serbian', + sk:'Slovak', + sl:'Slovenian', + es:'Spanish', + sw:'Swahili', + sv:'Swedish', + tl:'Tagalog', + th:'Thai', + tr:'Turkish', + uk:'Ukrainian', + vi:'Vietnamese', + cy:'Welsh', + yi:'Yiddish' + } + }; + + /* + Parses WebVTT format which should be formatted as + ================================ + WEBVTT + + 1 + 00:00:01,1 --> 00:00:05,000 + A line of text + + 2 + 00:01:15,1 --> 00:02:05,000 + A second line of text + + =============================== + + Adapted from: http://www.delphiki.com/html5/playr + */ + mejs.TrackFormatParser = { + webvtt: { + pattern_timecode: /^((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, + + parse: function(trackText) { + var + i = 0, + lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/), + entries = {text:[], times:[]}, + timecode, + text, + identifier; + for(; i<lines.length; i++) { + timecode = this.pattern_timecode.exec(lines[i]); + + if (timecode && i<lines.length) { + if ((i - 1) >= 0 && lines[i - 1] !== '') { + identifier = lines[i - 1]; + } + i++; + // grab all the (possibly multi-line) text that follows + text = lines[i]; + i++; + while(lines[i] !== '' && i<lines.length){ + text = text + '\n' + lines[i]; + i++; + } + text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); + // Text is in a different array so I can use .join + entries.text.push(text); + entries.times.push( + { + identifier: identifier, + start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) === 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]), + stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]), + settings: timecode[5] + }); + } + identifier = ''; + } + return entries; + } + }, + // Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420 + dfxp: { + parse: function(trackText) { + trackText = $(trackText).filter("tt"); + var + i = 0, + container = trackText.children("div").eq(0), + lines = container.find("p"), + styleNode = trackText.find("#" + container.attr("style")), + styles, + begin, + end, + text, + entries = {text:[], times:[]}; + + + if (styleNode.length) { + var attributes = styleNode.removeAttr("id").get(0).attributes; + if (attributes.length) { + styles = {}; + for (i = 0; i < attributes.length; i++) { + styles[attributes[i].name.split(":")[1]] = attributes[i].value; + } + } + } + + for(i = 0; i<lines.length; i++) { + var style; + var _temp_times = { + start: null, + stop: null, + style: null + }; + if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin")); + if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end")); + if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end")); + if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin")); + if (styles) { + style = ""; + for (var _style in styles) { + style += _style + ":" + styles[_style] + ";"; + } + } + if (style) _temp_times.style = style; + if (_temp_times.start === 0) _temp_times.start = 0.200; + entries.times.push(_temp_times); + text = $.trim(lines.eq(i).html()).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); + entries.text.push(text); + if (entries.times.start === 0) entries.times.start = 2; + } + return entries; + } + }, + split2: function (text, regex) { + // normal version for compliant browsers + // see below for IE fix + return text.split(regex); + } + }; + + // test for browsers with bad String.split method. + if ('x\n\ny'.split(/\n/gi).length != 3) { + // add super slow IE8 and below version + mejs.TrackFormatParser.split2 = function(text, regex) { + var + parts = [], + chunk = '', + i; + + for (i=0; i<text.length; i++) { + chunk += text.substring(i,i+1); + if (regex.test(chunk)) { + parts.push(chunk.replace(regex, '')); + chunk = ''; + } + } + parts.push(chunk); + return parts; + }; + } + +})(mejs.$); + +/* +* ContextMenu Plugin +* +* +*/ + +(function($) { + +$.extend(mejs.MepDefaults, + { 'contextMenuItems': [ + // demo of a fullscreen option + { + render: function(player) { + + // check for fullscreen plugin + if (typeof player.enterFullScreen == 'undefined') + return null; + + if (player.isFullScreen) { + return mejs.i18n.t('Turn off Fullscreen'); + } else { + return mejs.i18n.t('Go Fullscreen'); + } + }, + click: function(player) { + if (player.isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + } + } + , + // demo of a mute/unmute button + { + render: function(player) { + if (player.media.muted) { + return mejs.i18n.t('Unmute'); + } else { + return mejs.i18n.t('Mute'); + } + }, + click: function(player) { + if (player.media.muted) { + player.setMuted(false); + } else { + player.setMuted(true); + } + } + }, + // separator + { + isSeparator: true + } + , + // demo of simple download video + { + render: function(player) { + return mejs.i18n.t('Download Video'); + }, + click: function(player) { + window.location.href = player.media.currentSrc; + } + } + ]} +); + + + $.extend(MediaElementPlayer.prototype, { + buildcontextmenu: function(player, controls, layers, media) { + + // create context menu + player.contextMenu = $('<div class="mejs-contextmenu"></div>') + .appendTo($('body')) + .hide(); + + // create events for showing context menu + player.container.bind('contextmenu', function(e) { + if (player.isContextMenuEnabled) { + e.preventDefault(); + player.renderContextMenu(e.clientX-1, e.clientY-1); + return false; + } + }); + player.container.bind('click', function() { + player.contextMenu.hide(); + }); + player.contextMenu.bind('mouseleave', function() { + + // + player.startContextMenuTimer(); + + }); + }, + + cleancontextmenu: function(player) { + player.contextMenu.remove(); + }, + + isContextMenuEnabled: true, + enableContextMenu: function() { + this.isContextMenuEnabled = true; + }, + disableContextMenu: function() { + this.isContextMenuEnabled = false; + }, + + contextMenuTimeout: null, + startContextMenuTimer: function() { + // + + var t = this; + + t.killContextMenuTimer(); + + t.contextMenuTimer = setTimeout(function() { + t.hideContextMenu(); + t.killContextMenuTimer(); + }, 750); + }, + killContextMenuTimer: function() { + var timer = this.contextMenuTimer; + + // + + if (timer != null) { + clearTimeout(timer); + delete timer; + timer = null; + } + }, + + hideContextMenu: function() { + this.contextMenu.hide(); + }, + + renderContextMenu: function(x,y) { + + // alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly + var t = this, + html = '', + items = t.options.contextMenuItems; + + for (var i=0, il=items.length; i<il; i++) { + + if (items[i].isSeparator) { + html += '<div class="mejs-contextmenu-separator"></div>'; + } else { + + var rendered = items[i].render(t); + + // render can return null if the item doesn't need to be used at the moment + if (rendered != null) { + html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>'; + } + } + } + + // position and show the context menu + t.contextMenu + .empty() + .append($(html)) + .css({top:y, left:x}) + .show(); + + // bind events + t.contextMenu.find('.mejs-contextmenu-item').each(function() { + + // which one is this? + var $dom = $(this), + itemIndex = parseInt( $dom.data('itemindex'), 10 ), + item = t.options.contextMenuItems[itemIndex]; + + // bind extra functionality? + if (typeof item.show != 'undefined') + item.show( $dom , t); + + // bind click action + $dom.click(function() { + // perform click action + if (typeof item.click != 'undefined') + item.click(t); + + // close + t.contextMenu.hide(); + }); + }); + + // stop the controls from hiding + setTimeout(function() { + t.killControlsTimer('rev3'); + }, 100); + + } + }); + +})(mejs.$); +/** + * Postroll plugin + */ +(function($) { + + $.extend(mejs.MepDefaults, { + postrollCloseText: mejs.i18n.t('Close') + }); + + // Postroll + $.extend(MediaElementPlayer.prototype, { + buildpostroll: function(player, controls, layers, media) { + var + t = this, + postrollLink = t.container.find('link[rel="postroll"]').attr('href'); + + if (typeof postrollLink !== 'undefined') { + player.postroll = + $('<div class="mejs-postroll-layer mejs-layer"><a class="mejs-postroll-close" onclick="$(this).parent().hide();return false;">' + t.options.postrollCloseText + '</a><div class="mejs-postroll-layer-content"></div></div>').prependTo(layers).hide(); + + t.media.addEventListener('ended', function (e) { + $.ajax({ + dataType: 'html', + url: postrollLink, + success: function (data, textStatus) { + layers.find('.mejs-postroll-layer-content').html(data); + } + }); + player.postroll.show(); + }, false); + } + } + }); + +})(mejs.$); diff --git a/js/mediaelement/build/mediaelementplayer.min.css b/js/mediaelement/build/mediaelementplayer.min.css new file mode 100644 index 0000000000000000000000000000000000000000..ca859f38c40629a861f1dcd252afb0cd07a9b39f --- /dev/null +++ b/js/mediaelement/build/mediaelementplayer.min.css @@ -0,0 +1 @@ +.mejs-container{position:relative;background:#000;font-family:Helvetica,Arial;text-align:left;vertical-align:top;text-indent:0;}.me-plugin{position:absolute;height:auto;width:auto;}.mejs-embed,.mejs-embed body{width:100%;height:100%;margin:0;padding:0;background:#000;overflow:hidden;}.mejs-fullscreen{overflow:hidden!important;}.mejs-container-fullscreen{position:fixed;left:0;top:0;right:0;bottom:0;overflow:hidden;z-index:1000;}.mejs-container-fullscreen .mejs-mediaelement,.mejs-container-fullscreen video{width:100%;height:100%;}.mejs-clear{clear:both;}.mejs-background{position:absolute;top:0;left:0;}.mejs-mediaelement{position:absolute;top:0;left:0;width:100%;height:100%;}.mejs-poster{position:absolute;top:0;left:0;background-size:contain;background-position:50% 50%;background-repeat:no-repeat;}:root .mejs-poster img{display:none;}.mejs-poster img{border:0;padding:0;border:0;}.mejs-overlay{position:absolute;top:0;left:0;}.mejs-overlay-play{cursor:pointer;}.mejs-overlay-button{position:absolute;top:50%;left:50%;width:100px;height:100px;margin:-50px 0 0 -50px;background:url(bigplay.svg) no-repeat;}.no-svg .mejs-overlay-button{background-image:url(bigplay.png);}.mejs-overlay:hover .mejs-overlay-button{background-position:0 -100px;}.mejs-overlay-loading{position:absolute;top:50%;left:50%;width:80px;height:80px;margin:-40px 0 0 -40px;background:#333;background:url(background.png);background:rgba(0,0,0,0.9);background:-webkit-gradient(linear,0% 0,0% 100%,from(rgba(50,50,50,0.9)),to(rgba(0,0,0,0.9)));background:-webkit-linear-gradient(top,rgba(50,50,50,0.9),rgba(0,0,0,0.9));background:-moz-linear-gradient(top,rgba(50,50,50,0.9),rgba(0,0,0,0.9));background:-o-linear-gradient(top,rgba(50,50,50,0.9),rgba(0,0,0,0.9));background:-ms-linear-gradient(top,rgba(50,50,50,0.9),rgba(0,0,0,0.9));background:linear-gradient(rgba(50,50,50,0.9),rgba(0,0,0,0.9));}.mejs-overlay-loading span{display:block;width:80px;height:80px;background:transparent url(loading.gif) 50% 50% no-repeat;}.mejs-container .mejs-controls{position:absolute;list-style-type:none;margin:0;padding:0;bottom:0;left:0;background:url(background.png);background:rgba(0,0,0,0.7);background:-webkit-gradient(linear,0% 0,0% 100%,from(rgba(50,50,50,0.7)),to(rgba(0,0,0,0.7)));background:-webkit-linear-gradient(top,rgba(50,50,50,0.7),rgba(0,0,0,0.7));background:-moz-linear-gradient(top,rgba(50,50,50,0.7),rgba(0,0,0,0.7));background:-o-linear-gradient(top,rgba(50,50,50,0.7),rgba(0,0,0,0.7));background:-ms-linear-gradient(top,rgba(50,50,50,0.7),rgba(0,0,0,0.7));background:linear-gradient(rgba(50,50,50,0.7),rgba(0,0,0,0.7));height:30px;width:100%;}.mejs-container .mejs-controls div{list-style-type:none;background-image:none;display:block;float:left;margin:0;padding:0;width:26px;height:26px;font-size:11px;line-height:11px;font-family:Helvetica,Arial;border:0;}.mejs-controls .mejs-button button{cursor:pointer;display:block;font-size:0;line-height:0;text-decoration:none;margin:7px 5px;padding:0;position:absolute;height:16px;width:16px;border:0;background:transparent url(controls.svg) no-repeat;}.no-svg .mejs-controls .mejs-button button{background-image:url(controls.png);}.mejs-controls .mejs-button button:focus{outline:dotted 1px #999;}.mejs-container .mejs-controls .mejs-time{color:#fff;display:block;height:17px;width:auto;padding:8px 3px 0 3px;overflow:hidden;text-align:center;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}.mejs-container .mejs-controls .mejs-time span{color:#fff;font-size:11px;line-height:12px;display:block;float:left;margin:1px 2px 0 0;width:auto;}.mejs-controls .mejs-play button{background-position:0 0;}.mejs-controls .mejs-pause button{background-position:0 -16px;}.mejs-controls .mejs-stop button{background-position:-112px 0;}.mejs-controls div.mejs-time-rail{direction:ltr;width:200px;padding-top:5px;}.mejs-controls .mejs-time-rail span{display:block;position:absolute;width:180px;height:10px;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;cursor:pointer;}.mejs-controls .mejs-time-rail .mejs-time-total{margin:5px;background:#333;background:rgba(50,50,50,0.8);background:-webkit-gradient(linear,0% 0,0% 100%,from(rgba(30,30,30,0.8)),to(rgba(60,60,60,0.8)));background:-webkit-linear-gradient(top,rgba(30,30,30,0.8),rgba(60,60,60,0.8));background:-moz-linear-gradient(top,rgba(30,30,30,0.8),rgba(60,60,60,0.8));background:-o-linear-gradient(top,rgba(30,30,30,0.8),rgba(60,60,60,0.8));background:-ms-linear-gradient(top,rgba(30,30,30,0.8),rgba(60,60,60,0.8));background:linear-gradient(rgba(30,30,30,0.8),rgba(60,60,60,0.8));}.mejs-controls .mejs-time-rail .mejs-time-buffering{width:100%;background-image:-o-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-ms-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:15px 15px;-moz-background-size:15px 15px;-o-background-size:15px 15px;background-size:15px 15px;-webkit-animation:buffering-stripes 2s linear infinite;-moz-animation:buffering-stripes 2s linear infinite;-ms-animation:buffering-stripes 2s linear infinite;-o-animation:buffering-stripes 2s linear infinite;animation:buffering-stripes 2s linear infinite;}@-webkit-keyframes buffering-stripes{from{background-position:0 0;}to{background-position:30px 0;}}@-moz-keyframes buffering-stripes{from{background-position:0 0;}to{background-position:30px 0;}}@-ms-keyframes buffering-stripes{from{background-position:0 0;}to{background-position:30px 0;}}@-o-keyframes buffering-stripes{from{background-position:0 0;}to{background-position:30px 0;}}@keyframes buffering-stripes{from{background-position:0 0;}to{background-position:30px 0;}}.mejs-controls .mejs-time-rail .mejs-time-loaded{background:#3caac8;background:rgba(60,170,200,0.8);background:-webkit-gradient(linear,0% 0,0% 100%,from(rgba(44,124,145,0.8)),to(rgba(78,183,212,0.8)));background:-webkit-linear-gradient(top,rgba(44,124,145,0.8),rgba(78,183,212,0.8));background:-moz-linear-gradient(top,rgba(44,124,145,0.8),rgba(78,183,212,0.8));background:-o-linear-gradient(top,rgba(44,124,145,0.8),rgba(78,183,212,0.8));background:-ms-linear-gradient(top,rgba(44,124,145,0.8),rgba(78,183,212,0.8));background:linear-gradient(rgba(44,124,145,0.8),rgba(78,183,212,0.8));width:0;}.mejs-controls .mejs-time-rail .mejs-time-current{background:#fff;background:rgba(255,255,255,0.8);background:-webkit-gradient(linear,0% 0,0% 100%,from(rgba(255,255,255,0.9)),to(rgba(200,200,200,0.8)));background:-webkit-linear-gradient(top,rgba(255,255,255,0.9),rgba(200,200,200,0.8));background:-moz-linear-gradient(top,rgba(255,255,255,0.9),rgba(200,200,200,0.8));background:-o-linear-gradient(top,rgba(255,255,255,0.9),rgba(200,200,200,0.8));background:-ms-linear-gradient(top,rgba(255,255,255,0.9),rgba(200,200,200,0.8));background:linear-gradient(rgba(255,255,255,0.9),rgba(200,200,200,0.8));width:0;}.mejs-controls .mejs-time-rail .mejs-time-handle{display:none;position:absolute;margin:0;width:10px;background:#fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;cursor:pointer;border:solid 2px #333;top:-2px;text-align:center;}.mejs-controls .mejs-time-rail .mejs-time-float{position:absolute;display:none;background:#eee;width:36px;height:17px;border:solid 1px #333;top:-26px;margin-left:-18px;text-align:center;color:#111;}.mejs-controls .mejs-time-rail .mejs-time-float-current{margin:2px;width:30px;display:block;text-align:center;left:0;}.mejs-controls .mejs-time-rail .mejs-time-float-corner{position:absolute;display:block;width:0;height:0;line-height:0;border:solid 5px #eee;border-color:#eee transparent transparent transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;top:15px;left:13px;}.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float{width:48px;}.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float-current{width:44px;}.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float-corner{left:18px;}.mejs-controls .mejs-fullscreen-button button{background-position:-32px 0;}.mejs-controls .mejs-unfullscreen button{background-position:-32px -16px;}.mejs-controls .mejs-mute button{background-position:-16px -16px;}.mejs-controls .mejs-unmute button{background-position:-16px 0;}.mejs-controls .mejs-volume-button{position:relative;}.mejs-controls .mejs-volume-button .mejs-volume-slider{display:none;height:115px;width:25px;background:url(background.png);background:rgba(50,50,50,0.7);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;top:-115px;left:0;z-index:1;position:absolute;margin:0;}.mejs-controls .mejs-volume-button:hover{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-total{position:absolute;left:11px;top:8px;width:2px;height:100px;background:#ddd;background:rgba(255,255,255,0.5);margin:0;}.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-current{position:absolute;left:11px;top:8px;width:2px;height:100px;background:#ddd;background:rgba(255,255,255,0.9);margin:0;}.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-handle{position:absolute;left:4px;top:-3px;width:16px;height:6px;background:#ddd;background:rgba(255,255,255,0.9);cursor:N-resize;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;margin:0;}.mejs-controls div.mejs-horizontal-volume-slider{height:26px;width:60px;position:relative;}.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-total{position:absolute;left:0;top:11px;width:50px;height:8px;margin:0;padding:0;font-size:1px;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;background:#333;background:rgba(50,50,50,0.8);background:-webkit-gradient(linear,0% 0,0% 100%,from(rgba(30,30,30,0.8)),to(rgba(60,60,60,0.8)));background:-webkit-linear-gradient(top,rgba(30,30,30,0.8),rgba(60,60,60,0.8));background:-moz-linear-gradient(top,rgba(30,30,30,0.8),rgba(60,60,60,0.8));background:-o-linear-gradient(top,rgba(30,30,30,0.8),rgba(60,60,60,0.8));background:-ms-linear-gradient(top,rgba(30,30,30,0.8),rgba(60,60,60,0.8));background:linear-gradient(rgba(30,30,30,0.8),rgba(60,60,60,0.8));}.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-current{position:absolute;left:0;top:11px;width:50px;height:8px;margin:0;padding:0;font-size:1px;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;background:#fff;background:rgba(255,255,255,0.8);background:-webkit-gradient(linear,0% 0,0% 100%,from(rgba(255,255,255,0.9)),to(rgba(200,200,200,0.8)));background:-webkit-linear-gradient(top,rgba(255,255,255,0.9),rgba(200,200,200,0.8));background:-moz-linear-gradient(top,rgba(255,255,255,0.9),rgba(200,200,200,0.8));background:-o-linear-gradient(top,rgba(255,255,255,0.9),rgba(200,200,200,0.8));background:-ms-linear-gradient(top,rgba(255,255,255,0.9),rgba(200,200,200,0.8));background:linear-gradient(rgba(255,255,255,0.9),rgba(200,200,200,0.8));}.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-handle{display:none;}.mejs-controls .mejs-captions-button{position:relative;}.mejs-controls .mejs-captions-button button{background-position:-48px 0;}.mejs-controls .mejs-captions-button .mejs-captions-selector{visibility:hidden;position:absolute;bottom:26px;right:-51px;width:85px;height:100px;background:url(background.png);background:rgba(50,50,50,0.7);border:solid 1px transparent;padding:10px 10px 0 10px;overflow:hidden;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}.mejs-controls .mejs-captions-button .mejs-captions-selector ul{margin:0;padding:0;display:block;list-style-type:none!important;overflow:hidden;}.mejs-controls .mejs-captions-button .mejs-captions-selector ul li{margin:0 0 6px 0;padding:0;list-style-type:none!important;display:block;color:#fff;overflow:hidden;}.mejs-controls .mejs-captions-button .mejs-captions-selector ul li input{clear:both;float:left;margin:3px 3px 0 5px;}.mejs-controls .mejs-captions-button .mejs-captions-selector ul li label{width:55px;float:left;padding:4px 0 0 0;line-height:15px;font-family:helvetica,arial;font-size:10px;}.mejs-controls .mejs-captions-button .mejs-captions-translations{font-size:10px;margin:0 0 5px 0;}.mejs-chapters{position:absolute;top:0;left:0;-xborder-right:solid 1px #fff;width:10000px;z-index:1;}.mejs-chapters .mejs-chapter{position:absolute;float:left;background:#222;background:rgba(0,0,0,0.7);background:-webkit-gradient(linear,0% 0,0% 100%,from(rgba(50,50,50,0.7)),to(rgba(0,0,0,0.7)));background:-webkit-linear-gradient(top,rgba(50,50,50,0.7),rgba(0,0,0,0.7));background:-moz-linear-gradient(top,rgba(50,50,50,0.7),rgba(0,0,0,0.7));background:-o-linear-gradient(top,rgba(50,50,50,0.7),rgba(0,0,0,0.7));background:-ms-linear-gradient(top,rgba(50,50,50,0.7),rgba(0,0,0,0.7));background:linear-gradient(rgba(50,50,50,0.7),rgba(0,0,0,0.7));filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,startColorstr=#323232,endColorstr=#000000);overflow:hidden;border:0;}.mejs-chapters .mejs-chapter .mejs-chapter-block{font-size:11px;color:#fff;padding:5px;display:block;border-right:solid 1px #333;border-bottom:solid 1px #333;cursor:pointer;}.mejs-chapters .mejs-chapter .mejs-chapter-block-last{border-right:none;}.mejs-chapters .mejs-chapter .mejs-chapter-block:hover{background:#666;background:rgba(102,102,102,0.7);background:-webkit-gradient(linear,0% 0,0% 100%,from(rgba(102,102,102,0.7)),to(rgba(50,50,50,0.6)));background:-webkit-linear-gradient(top,rgba(102,102,102,0.7),rgba(50,50,50,0.6));background:-moz-linear-gradient(top,rgba(102,102,102,0.7),rgba(50,50,50,0.6));background:-o-linear-gradient(top,rgba(102,102,102,0.7),rgba(50,50,50,0.6));background:-ms-linear-gradient(top,rgba(102,102,102,0.7),rgba(50,50,50,0.6));background:linear-gradient(rgba(102,102,102,0.7),rgba(50,50,50,0.6));filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,startColorstr=#666666,endColorstr=#323232);}.mejs-chapters .mejs-chapter .mejs-chapter-block .ch-title{font-size:12px;font-weight:bold;display:block;white-space:nowrap;text-overflow:ellipsis;margin:0 0 3px 0;line-height:12px;}.mejs-chapters .mejs-chapter .mejs-chapter-block .ch-timespan{font-size:12px;line-height:12px;margin:3px 0 4px 0;display:block;white-space:nowrap;text-overflow:ellipsis;}.mejs-captions-layer{position:absolute;bottom:0;left:0;text-align:center;line-height:20px;font-size:16px;color:#fff;}.mejs-captions-layer a{color:#fff;text-decoration:underline;}.mejs-captions-layer[lang=ar]{font-size:20px;font-weight:normal;}.mejs-captions-position{position:absolute;width:100%;bottom:15px;left:0;}.mejs-captions-position-hover{bottom:35px;}.mejs-captions-text{padding:3px 5px;background:url(background.png);background:rgba(20,20,20,0.5);white-space:pre-wrap;}.me-cannotplay a{color:#fff;font-weight:bold;}.me-cannotplay span{padding:15px;display:block;}.mejs-controls .mejs-loop-off button{background-position:-64px -16px;}.mejs-controls .mejs-loop-on button{background-position:-64px 0;}.mejs-controls .mejs-backlight-off button{background-position:-80px -16px;}.mejs-controls .mejs-backlight-on button{background-position:-80px 0;}.mejs-controls .mejs-picturecontrols-button{background-position:-96px 0;}.mejs-contextmenu{position:absolute;width:150px;padding:10px;border-radius:4px;top:0;left:0;background:#fff;border:solid 1px #999;z-index:1001;}.mejs-contextmenu .mejs-contextmenu-separator{height:1px;font-size:0;margin:5px 6px;background:#333;}.mejs-contextmenu .mejs-contextmenu-item{font-family:Helvetica,Arial;font-size:12px;padding:4px 6px;cursor:pointer;color:#333;}.mejs-contextmenu .mejs-contextmenu-item:hover{background:#2C7C91;color:#fff;}.mejs-controls .mejs-sourcechooser-button{position:relative;}.mejs-controls .mejs-sourcechooser-button button{background-position:-128px 0;}.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector{visibility:hidden;position:absolute;bottom:26px;right:-10px;width:130px;height:100px;background:url(background.png);background:rgba(50,50,50,0.7);border:solid 1px transparent;padding:10px;overflow:hidden;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul{margin:0;padding:0;display:block;list-style-type:none!important;overflow:hidden;}.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li{margin:0 0 6px 0;padding:0;list-style-type:none!important;display:block;color:#fff;overflow:hidden;}.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li input{clear:both;float:left;margin:3px 3px 0 5px;}.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li label{width:100px;float:left;padding:4px 0 0 0;line-height:15px;font-family:helvetica,arial;font-size:10px;}.mejs-postroll-layer{position:absolute;bottom:0;left:0;width:100%;height:100%;background:url(background.png);background:rgba(50,50,50,0.7);z-index:1000;overflow:hidden;}.mejs-postroll-layer-content{width:100%;height:100%;}.mejs-postroll-close{position:absolute;right:0;top:0;background:url(background.png);background:rgba(50,50,50,0.7);color:#fff;padding:4px;z-index:100;cursor:pointer;}div.mejs-speed-button{width:46px!important;position:relative;}.mejs-controls .mejs-button.mejs-speed-button button{background:transparent;width:36px;font-size:11px;line-height:normal;color:#fff;}.mejs-controls .mejs-speed-button .mejs-speed-selector{visibility:hidden;position:absolute;top:-100px;left:-10px;width:60px;height:100px;background:url(background.png);background:rgba(50,50,50,0.7);border:solid 1px transparent;padding:0;overflow:hidden;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}.mejs-controls .mejs-speed-button:hover>.mejs-speed-selector{visibility:visible;}.mejs-controls .mejs-speed-button .mejs-speed-selector ul li label.mejs-speed-selected{color:rgba(33,248,248,1);}.mejs-controls .mejs-speed-button .mejs-speed-selector ul{margin:0;padding:0;display:block;list-style-type:none!important;overflow:hidden;}.mejs-controls .mejs-speed-button .mejs-speed-selector ul li{margin:0 0 6px 0;padding:0 10px;list-style-type:none!important;display:block;color:#fff;overflow:hidden;}.mejs-controls .mejs-speed-button .mejs-speed-selector ul li input{clear:both;float:left;margin:3px 3px 0 5px;display:none;}.mejs-controls .mejs-speed-button .mejs-speed-selector ul li label{width:60px;float:left;padding:4px 0 0 0;line-height:15px;font-family:helvetica,arial;font-size:11.5px;color:white;margin-left:5px;cursor:pointer;}.mejs-controls .mejs-speed-button .mejs-speed-selector ul li:hover{background-color:#c8c8c8!important;background-color:rgba(255,255,255,.4)!important;} \ No newline at end of file diff --git a/js/mediaelement/build/mediaelementplayer.min.js b/js/mediaelement/build/mediaelementplayer.min.js new file mode 100644 index 0000000000000000000000000000000000000000..e96ae83b2cfae6b2aae28efc4a23b509e29b72aa --- /dev/null +++ b/js/mediaelement/build/mediaelementplayer.min.js @@ -0,0 +1,109 @@ +/*! + * MediaElementPlayer + * http://mediaelementjs.com/ + * + * Creates a controller bar for HTML5 <video> add <audio> tags + * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) + * + * Copyright 2010-2013, John Dyer (http://j.hn/) + * License: MIT + * + */if(typeof jQuery!="undefined")mejs.$=jQuery;else if(typeof ender!="undefined")mejs.$=ender; +(function(f){mejs.MepDefaults={poster:"",showPosterWhenEnded:false,defaultVideoWidth:480,defaultVideoHeight:270,videoWidth:-1,videoHeight:-1,defaultAudioWidth:400,defaultAudioHeight:30,defaultSeekBackwardInterval:function(a){return a.duration*0.05},defaultSeekForwardInterval:function(a){return a.duration*0.05},setDimensions:true,audioWidth:-1,audioHeight:-1,startVolume:0.8,loop:false,autoRewind:true,enableAutosize:true,alwaysShowHours:false,showTimecodeFrameCount:false,framesPerSecond:25,autosizeProgress:true, +alwaysShowControls:false,hideVideoControlsOnLoad:false,clickToPlayPause:true,iPadUseNativeControls:false,iPhoneUseNativeControls:false,AndroidUseNativeControls:false,features:["playpause","current","progress","duration","tracks","volume","fullscreen"],isVideo:true,enableKeyboard:true,pauseOtherPlayers:true,keyActions:[{keys:[32,179],action:function(a,b){b.paused||b.ended?a.play():a.pause()}},{keys:[38],action:function(a,b){a.container.find(".mejs-volume-slider").css("display","block");if(a.isVideo){a.showControls(); +a.startControlsTimer()}b.setVolume(Math.min(b.volume+0.1,1))}},{keys:[40],action:function(a,b){a.container.find(".mejs-volume-slider").css("display","block");if(a.isVideo){a.showControls();a.startControlsTimer()}b.setVolume(Math.max(b.volume-0.1,0))}},{keys:[37,227],action:function(a,b){if(!isNaN(b.duration)&&b.duration>0){if(a.isVideo){a.showControls();a.startControlsTimer()}var c=Math.max(b.currentTime-a.options.defaultSeekBackwardInterval(b),0);b.setCurrentTime(c)}}},{keys:[39,228],action:function(a, +b){if(!isNaN(b.duration)&&b.duration>0){if(a.isVideo){a.showControls();a.startControlsTimer()}var c=Math.min(b.currentTime+a.options.defaultSeekForwardInterval(b),b.duration);b.setCurrentTime(c)}}},{keys:[70],action:function(a){if(typeof a.enterFullScreen!="undefined")a.isFullScreen?a.exitFullScreen():a.enterFullScreen()}},{keys:[77],action:function(a){a.container.find(".mejs-volume-slider").css("display","block");if(a.isVideo){a.showControls();a.startControlsTimer()}a.media.muted?a.setMuted(false): +a.setMuted(true)}}]};mejs.mepIndex=0;mejs.players={};mejs.MediaElementPlayer=function(a,b){if(!(this instanceof mejs.MediaElementPlayer))return new mejs.MediaElementPlayer(a,b);this.$media=this.$node=f(a);this.node=this.media=this.$media[0];if(typeof this.node.player!="undefined")return this.node.player;else this.node.player=this;if(typeof b=="undefined")b=this.$node.data("mejsoptions");this.options=f.extend({},mejs.MepDefaults,b);this.id="mep_"+mejs.mepIndex++;mejs.players[this.id]=this;this.init(); +return this};mejs.MediaElementPlayer.prototype={hasFocus:false,controlsAreVisible:true,init:function(){var a=this,b=mejs.MediaFeatures,c=f.extend(true,{},a.options,{success:function(d,g){a.meReady(d,g)},error:function(d){a.handleError(d)}}),e=a.media.tagName.toLowerCase();a.isDynamic=e!=="audio"&&e!=="video";a.isVideo=a.isDynamic?a.options.isVideo:e!=="audio"&&a.options.isVideo;if(b.isiPad&&a.options.iPadUseNativeControls||b.isiPhone&&a.options.iPhoneUseNativeControls){a.$media.attr("controls","controls"); +b.isiPad&&a.media.getAttribute("autoplay")!==null&&a.play()}else if(!(b.isAndroid&&a.options.AndroidUseNativeControls)){a.$media.removeAttr("controls");a.container=f('<div id="'+a.id+'" class="mejs-container '+(mejs.MediaFeatures.svg?"svg":"no-svg")+'"><div class="mejs-inner"><div class="mejs-mediaelement"></div><div class="mejs-layers"></div><div class="mejs-controls"></div><div class="mejs-clear"></div></div></div>').addClass(a.$media[0].className).insertBefore(a.$media);a.container.addClass((b.isAndroid? +"mejs-android ":"")+(b.isiOS?"mejs-ios ":"")+(b.isiPad?"mejs-ipad ":"")+(b.isiPhone?"mejs-iphone ":"")+(a.isVideo?"mejs-video ":"mejs-audio "));if(b.isiOS){b=a.$media.clone();a.container.find(".mejs-mediaelement").append(b);a.$media.remove();a.$node=a.$media=b;a.node=a.media=b[0]}else a.container.find(".mejs-mediaelement").append(a.$media);a.controls=a.container.find(".mejs-controls");a.layers=a.container.find(".mejs-layers");b=a.isVideo?"video":"audio";e=b.substring(0,1).toUpperCase()+b.substring(1); +a.width=a.options[b+"Width"]>0||a.options[b+"Width"].toString().indexOf("%")>-1?a.options[b+"Width"]:a.media.style.width!==""&&a.media.style.width!==null?a.media.style.width:a.media.getAttribute("width")!==null?a.$media.attr("width"):a.options["default"+e+"Width"];a.height=a.options[b+"Height"]>0||a.options[b+"Height"].toString().indexOf("%")>-1?a.options[b+"Height"]:a.media.style.height!==""&&a.media.style.height!==null?a.media.style.height:a.$media[0].getAttribute("height")!==null?a.$media.attr("height"): +a.options["default"+e+"Height"];a.setPlayerSize(a.width,a.height);c.pluginWidth=a.width;c.pluginHeight=a.height}mejs.MediaElement(a.$media[0],c);typeof a.container!="undefined"&&a.controlsAreVisible&&a.container.trigger("controlsshown")},showControls:function(a){var b=this;a=typeof a=="undefined"||a;if(!b.controlsAreVisible){if(a){b.controls.css("visibility","visible").stop(true,true).fadeIn(200,function(){b.controlsAreVisible=true;b.container.trigger("controlsshown")});b.container.find(".mejs-control").css("visibility", +"visible").stop(true,true).fadeIn(200,function(){b.controlsAreVisible=true})}else{b.controls.css("visibility","visible").css("display","block");b.container.find(".mejs-control").css("visibility","visible").css("display","block");b.controlsAreVisible=true;b.container.trigger("controlsshown")}b.setControlsSize()}},hideControls:function(a){var b=this;a=typeof a=="undefined"||a;if(!(!b.controlsAreVisible||b.options.alwaysShowControls))if(a){b.controls.stop(true,true).fadeOut(200,function(){f(this).css("visibility", +"hidden").css("display","block");b.controlsAreVisible=false;b.container.trigger("controlshidden")});b.container.find(".mejs-control").stop(true,true).fadeOut(200,function(){f(this).css("visibility","hidden").css("display","block")})}else{b.controls.css("visibility","hidden").css("display","block");b.container.find(".mejs-control").css("visibility","hidden").css("display","block");b.controlsAreVisible=false;b.container.trigger("controlshidden")}},controlsTimer:null,startControlsTimer:function(a){var b= +this;a=typeof a!="undefined"?a:1500;b.killControlsTimer("start");b.controlsTimer=setTimeout(function(){b.hideControls();b.killControlsTimer("hide")},a)},killControlsTimer:function(){if(this.controlsTimer!==null){clearTimeout(this.controlsTimer);delete this.controlsTimer;this.controlsTimer=null}},controlsEnabled:true,disableControls:function(){this.killControlsTimer();this.hideControls(false);this.controlsEnabled=false},enableControls:function(){this.showControls(false);this.controlsEnabled=true}, +meReady:function(a,b){var c=this,e=mejs.MediaFeatures,d=b.getAttribute("autoplay");d=!(typeof d=="undefined"||d===null||d==="false");var g;if(!c.created){c.created=true;c.media=a;c.domNode=b;if(!(e.isAndroid&&c.options.AndroidUseNativeControls)&&!(e.isiPad&&c.options.iPadUseNativeControls)&&!(e.isiPhone&&c.options.iPhoneUseNativeControls)){c.buildposter(c,c.controls,c.layers,c.media);c.buildkeyboard(c,c.controls,c.layers,c.media);c.buildoverlays(c,c.controls,c.layers,c.media);c.findTracks();for(g in c.options.features){e= +c.options.features[g];if(c["build"+e])try{c["build"+e](c,c.controls,c.layers,c.media)}catch(k){}}c.container.trigger("controlsready");c.setPlayerSize(c.width,c.height);c.setControlsSize();if(c.isVideo){if(mejs.MediaFeatures.hasTouch)c.$media.bind("touchstart",function(){if(c.controlsAreVisible)c.hideControls(false);else c.controlsEnabled&&c.showControls(false)});else{c.clickToPlayPauseCallback=function(){if(c.options.clickToPlayPause)c.media.paused?c.play():c.pause()};c.media.addEventListener("click", +c.clickToPlayPauseCallback,false);c.container.bind("mouseenter mouseover",function(){if(c.controlsEnabled)if(!c.options.alwaysShowControls){c.killControlsTimer("enter");c.showControls();c.startControlsTimer(2500)}}).bind("mousemove",function(){if(c.controlsEnabled){c.controlsAreVisible||c.showControls();c.options.alwaysShowControls||c.startControlsTimer(2500)}}).bind("mouseleave",function(){c.controlsEnabled&&!c.media.paused&&!c.options.alwaysShowControls&&c.startControlsTimer(1E3)})}c.options.hideVideoControlsOnLoad&& +c.hideControls(false);d&&!c.options.alwaysShowControls&&c.hideControls();c.options.enableAutosize&&c.media.addEventListener("loadedmetadata",function(j){if(c.options.videoHeight<=0&&c.domNode.getAttribute("height")===null&&!isNaN(j.target.videoHeight)){c.setPlayerSize(j.target.videoWidth,j.target.videoHeight);c.setControlsSize();c.media.setVideoSize(j.target.videoWidth,j.target.videoHeight)}},false)}a.addEventListener("play",function(){for(var j in mejs.players){var m=mejs.players[j];m.id!=c.id&& +c.options.pauseOtherPlayers&&!m.paused&&!m.ended&&m.pause();m.hasFocus=false}c.hasFocus=true},false);c.media.addEventListener("ended",function(){if(c.options.autoRewind)try{c.media.setCurrentTime(0)}catch(j){}c.media.pause();c.setProgressRail&&c.setProgressRail();c.setCurrentRail&&c.setCurrentRail();if(c.options.loop)c.play();else!c.options.alwaysShowControls&&c.controlsEnabled&&c.showControls()},false);c.media.addEventListener("loadedmetadata",function(){c.updateDuration&&c.updateDuration();c.updateCurrent&& +c.updateCurrent();if(!c.isFullScreen){c.setPlayerSize(c.width,c.height);c.setControlsSize()}},false);setTimeout(function(){c.setPlayerSize(c.width,c.height);c.setControlsSize()},50);c.globalBind("resize",function(){c.isFullScreen||mejs.MediaFeatures.hasTrueNativeFullScreen&&document.webkitIsFullScreen||c.setPlayerSize(c.width,c.height);c.setControlsSize()});c.media.pluginType=="youtube"&&c.options.autoplay&&c.container.find(".mejs-overlay-play").hide()}d&&a.pluginType=="native"&&c.play();if(c.options.success)typeof c.options.success== +"string"?window[c.options.success](c.media,c.domNode,c):c.options.success(c.media,c.domNode,c)}},handleError:function(a){this.controls.hide();this.options.error&&this.options.error(a)},setPlayerSize:function(a,b){if(!this.options.setDimensions)return false;if(typeof a!="undefined")this.width=a;if(typeof b!="undefined")this.height=b;if(this.height.toString().indexOf("%")>0||this.$node.css("max-width")==="100%"||this.$node[0].currentStyle&&this.$node[0].currentStyle.maxWidth==="100%"){var c=this.isVideo? +this.media.videoWidth&&this.media.videoWidth>0?this.media.videoWidth:this.media.getAttribute("width")!==null?this.media.getAttribute("width"):this.options.defaultVideoWidth:this.options.defaultAudioWidth,e=this.isVideo?this.media.videoHeight&&this.media.videoHeight>0?this.media.videoHeight:this.media.getAttribute("height")!==null?this.media.getAttribute("height"):this.options.defaultVideoHeight:this.options.defaultAudioHeight,d=this.container.parent().closest(":visible").width(),g=this.container.parent().closest(":visible").height(); +c=this.isVideo||!this.options.autosizeProgress?parseInt(d*e/c,10):e;if(isNaN(c)||g!=0&&c>g)c=g;if(this.container.parent()[0].tagName.toLowerCase()==="body"){d=f(window).width();c=f(window).height()}if(c!=0&&d!=0){this.container.width(d).height(c);this.$media.add(this.container.find(".mejs-shim")).width("100%").height("100%");this.isVideo&&this.media.setVideoSize&&this.media.setVideoSize(d,c);this.layers.children(".mejs-layer").width("100%").height("100%")}}else{this.container.width(this.width).height(this.height); +this.layers.children(".mejs-layer").width(this.width).height(this.height)}d=this.layers.find(".mejs-overlay-play");g=d.find(".mejs-overlay-button");d.height(this.container.height()-this.controls.height());g.css("margin-top","-"+(g.height()/2-this.controls.height()/2).toString()+"px")},setControlsSize:function(){var a=0,b=0,c=this.controls.find(".mejs-time-rail"),e=this.controls.find(".mejs-time-total");this.controls.find(".mejs-time-current");this.controls.find(".mejs-time-loaded");var d=c.siblings(), +g=d.last(),k=null;if(!(!this.container.is(":visible")||!c.length||!c.is(":visible"))){if(this.options&&!this.options.autosizeProgress)b=parseInt(c.css("width"));if(b===0||!b){d.each(function(){var j=f(this);if(j.css("position")!="absolute"&&j.is(":visible"))a+=f(this).outerWidth(true)});b=this.controls.width()-a-(c.outerWidth(true)-c.width())}do{c.width(b);e.width(b-(e.outerWidth(true)-e.width()));if(g.css("position")!="absolute"){k=g.position();b--}}while(k!=null&&k.top>0&&b>0);this.setProgressRail&& +this.setProgressRail();this.setCurrentRail&&this.setCurrentRail()}},buildposter:function(a,b,c,e){var d=f('<div class="mejs-poster mejs-layer"></div>').appendTo(c);b=a.$media.attr("poster");if(a.options.poster!=="")b=a.options.poster;b!==""&&b!=null?this.setPoster(b):d.hide();e.addEventListener("play",function(){d.hide()},false);a.options.showPosterWhenEnded&&a.options.autoRewind&&e.addEventListener("ended",function(){d.show()},false)},setPoster:function(a){var b=this.container.find(".mejs-poster"), +c=b.find("img");if(c.length==0)c=f('<img width="100%" height="100%" />').appendTo(b);c.attr("src",a);b.css({"background-image":"url("+a+")"})},buildoverlays:function(a,b,c,e){var d=this;if(a.isVideo){var g=f('<div class="mejs-overlay mejs-layer"><div class="mejs-overlay-loading"><span></span></div></div>').hide().appendTo(c),k=f('<div class="mejs-overlay mejs-layer"><div class="mejs-overlay-error"></div></div>').hide().appendTo(c),j=f('<div class="mejs-overlay mejs-layer mejs-overlay-play"><div class="mejs-overlay-button"></div></div>').appendTo(c).bind("click", +function(){d.options.clickToPlayPause&&e.paused&&e.play()});e.addEventListener("play",function(){j.hide();g.hide();b.find(".mejs-time-buffering").hide();k.hide()},false);e.addEventListener("playing",function(){j.hide();g.hide();b.find(".mejs-time-buffering").hide();k.hide()},false);e.addEventListener("seeking",function(){g.show();b.find(".mejs-time-buffering").show()},false);e.addEventListener("seeked",function(){g.hide();b.find(".mejs-time-buffering").hide()},false);e.addEventListener("pause",function(){mejs.MediaFeatures.isiPhone|| +j.show()},false);e.addEventListener("waiting",function(){g.show();b.find(".mejs-time-buffering").show()},false);e.addEventListener("loadeddata",function(){g.show();b.find(".mejs-time-buffering").show()},false);e.addEventListener("canplay",function(){g.hide();b.find(".mejs-time-buffering").hide()},false);e.addEventListener("error",function(){g.hide();b.find(".mejs-time-buffering").hide();k.show();k.find("mejs-overlay-error").html("Error loading this resource")},false);e.addEventListener("keydown", +function(m){d.onkeydown(a,e,m)},false)}},buildkeyboard:function(a,b,c,e){var d=this;d.globalBind("keydown",function(g){return d.onkeydown(a,e,g)});d.globalBind("click",function(g){a.hasFocus=f(g.target).closest(".mejs-container").length!=0})},onkeydown:function(a,b,c){if(a.hasFocus&&a.options.enableKeyboard)for(var e=0,d=a.options.keyActions.length;e<d;e++)for(var g=a.options.keyActions[e],k=0,j=g.keys.length;k<j;k++)if(c.keyCode==g.keys[k]){typeof c.preventDefault=="function"&&c.preventDefault(); +g.action(a,b,c.keyCode);return false}return true},findTracks:function(){var a=this,b=a.$media.find("track");a.tracks=[];b.each(function(c,e){e=f(e);a.tracks.push({srclang:e.attr("srclang")?e.attr("srclang").toLowerCase():"",src:e.attr("src"),kind:e.attr("kind"),label:e.attr("label")||"",entries:[],isLoaded:false})})},changeSkin:function(a){this.container[0].className="mejs-container "+a;this.setPlayerSize(this.width,this.height);this.setControlsSize()},play:function(){this.load();this.media.play()}, +pause:function(){try{this.media.pause()}catch(a){}},load:function(){this.isLoaded||this.media.load();this.isLoaded=true},setMuted:function(a){this.media.setMuted(a)},setCurrentTime:function(a){this.media.setCurrentTime(a)},getCurrentTime:function(){return this.media.currentTime},setVolume:function(a){this.media.setVolume(a)},getVolume:function(){return this.media.volume},setSrc:function(a){this.media.setSrc(a)},remove:function(){var a,b;for(a in this.options.features){b=this.options.features[a];if(this["clean"+ +b])try{this["clean"+b](this)}catch(c){}}if(this.isDynamic)this.$node.insertBefore(this.container);else{this.$media.prop("controls",true);this.$node.clone().insertBefore(this.container).show();this.$node.remove()}this.media.pluginType!=="native"&&this.media.remove();delete mejs.players[this.id];typeof this.container=="object"&&this.container.remove();this.globalUnbind();delete this.node.player}};(function(){function a(c,e){var d={d:[],w:[]};f.each((c||"").split(" "),function(g,k){var j=k+"."+e;if(j.indexOf(".")=== +0){d.d.push(j);d.w.push(j)}else d[b.test(k)?"w":"d"].push(j)});d.d=d.d.join(" ");d.w=d.w.join(" ");return d}var b=/^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/;mejs.MediaElementPlayer.prototype.globalBind=function(c,e,d){c=a(c,this.id);c.d&&f(document).bind(c.d,e,d);c.w&&f(window).bind(c.w,e,d)};mejs.MediaElementPlayer.prototype.globalUnbind=function(c,e){c=a(c,this.id);c.d&&f(document).unbind(c.d,e);c.w&&f(window).unbind(c.w,e)}})(); +if(typeof f!="undefined"){f.fn.mediaelementplayer=function(a){a===false?this.each(function(){var b=f(this).data("mediaelementplayer");b&&b.remove();f(this).removeData("mediaelementplayer")}):this.each(function(){f(this).data("mediaelementplayer",new mejs.MediaElementPlayer(this,a))});return this};f(document).ready(function(){f(".mejs-player").mediaelementplayer()})}window.MediaElementPlayer=mejs.MediaElementPlayer})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{playpauseText:mejs.i18n.t("Play/Pause")});f.extend(MediaElementPlayer.prototype,{buildplaypause:function(a,b,c,e){var d=f('<div class="mejs-button mejs-playpause-button mejs-play" ><button type="button" aria-controls="'+this.id+'" title="'+this.options.playpauseText+'" aria-label="'+this.options.playpauseText+'"></button></div>').appendTo(b).click(function(g){g.preventDefault();e.paused?e.play():e.pause();return false});e.addEventListener("play",function(){d.removeClass("mejs-play").addClass("mejs-pause")}, +false);e.addEventListener("playing",function(){d.removeClass("mejs-play").addClass("mejs-pause")},false);e.addEventListener("pause",function(){d.removeClass("mejs-pause").addClass("mejs-play")},false);e.addEventListener("paused",function(){d.removeClass("mejs-pause").addClass("mejs-play")},false)}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{stopText:"Stop"});f.extend(MediaElementPlayer.prototype,{buildstop:function(a,b,c,e){f('<div class="mejs-button mejs-stop-button mejs-stop"><button type="button" aria-controls="'+this.id+'" title="'+this.options.stopText+'" aria-label="'+this.options.stopText+'"></button></div>').appendTo(b).click(function(){e.paused||e.pause();if(e.currentTime>0){e.setCurrentTime(0);e.pause();b.find(".mejs-time-current").width("0px");b.find(".mejs-time-handle").css("left", +"0px");b.find(".mejs-time-float-current").html(mejs.Utility.secondsToTimeCode(0));b.find(".mejs-currenttime").html(mejs.Utility.secondsToTimeCode(0));c.find(".mejs-poster").show()}})}})})(mejs.$); +(function(f){f.extend(MediaElementPlayer.prototype,{buildprogress:function(a,b,c,e){f('<div class="mejs-time-rail"><span class="mejs-time-total"><span class="mejs-time-buffering"></span><span class="mejs-time-loaded"></span><span class="mejs-time-current"></span><span class="mejs-time-handle"></span><span class="mejs-time-float"><span class="mejs-time-float-current">00:00</span><span class="mejs-time-float-corner"></span></span></span></div>').appendTo(b);b.find(".mejs-time-buffering").hide();var d= +this,g=b.find(".mejs-time-total");c=b.find(".mejs-time-loaded");var k=b.find(".mejs-time-current"),j=b.find(".mejs-time-handle"),m=b.find(".mejs-time-float"),q=b.find(".mejs-time-float-current"),p=function(h){h=h.originalEvent.changedTouches?h.originalEvent.changedTouches[0].pageX:h.pageX;var l=g.offset(),r=g.outerWidth(true),n=0,o=n=0;if(e.duration){if(h<l.left)h=l.left;else if(h>r+l.left)h=r+l.left;o=h-l.left;n=o/r;n=n<=0.02?0:n*e.duration;t&&n!==e.currentTime&&e.setCurrentTime(n);if(!mejs.MediaFeatures.hasTouch){m.css("left", +o);q.html(mejs.Utility.secondsToTimeCode(n));m.show()}}},t=false;g.bind("mousedown touchstart",function(h){if(h.which===1||h.which===0){t=true;p(h);d.globalBind("mousemove.dur touchmove.dur",function(l){p(l)});d.globalBind("mouseup.dur touchend.dur",function(){t=false;m.hide();d.globalUnbind(".dur")});return false}}).bind("mouseenter",function(){d.globalBind("mousemove.dur",function(h){p(h)});mejs.MediaFeatures.hasTouch||m.show()}).bind("mouseleave",function(){if(!t){d.globalUnbind(".dur");m.hide()}}); +e.addEventListener("progress",function(h){a.setProgressRail(h);a.setCurrentRail(h)},false);e.addEventListener("timeupdate",function(h){a.setProgressRail(h);a.setCurrentRail(h)},false);d.loaded=c;d.total=g;d.current=k;d.handle=j},setProgressRail:function(a){var b=a!=undefined?a.target:this.media,c=null;if(b&&b.buffered&&b.buffered.length>0&&b.buffered.end&&b.duration)c=b.buffered.end(0)/b.duration;else if(b&&b.bytesTotal!=undefined&&b.bytesTotal>0&&b.bufferedBytes!=undefined)c=b.bufferedBytes/b.bytesTotal; +else if(a&&a.lengthComputable&&a.total!=0)c=a.loaded/a.total;if(c!==null){c=Math.min(1,Math.max(0,c));this.loaded&&this.total&&this.loaded.width(this.total.width()*c)}},setCurrentRail:function(){if(this.media.currentTime!=undefined&&this.media.duration)if(this.total&&this.handle){var a=Math.round(this.total.width()*this.media.currentTime/this.media.duration),b=a-Math.round(this.handle.outerWidth(true)/2);this.current.width(a);this.handle.css("left",b)}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{duration:-1,timeAndDurationSeparator:"<span> | </span>"});f.extend(MediaElementPlayer.prototype,{buildcurrent:function(a,b,c,e){f('<div class="mejs-time"><span class="mejs-currenttime">'+(a.options.alwaysShowHours?"00:":"")+(a.options.showTimecodeFrameCount?"00:00:00":"00:00")+"</span></div>").appendTo(b);this.currenttime=this.controls.find(".mejs-currenttime");e.addEventListener("timeupdate",function(){a.updateCurrent()},false)},buildduration:function(a,b, +c,e){if(b.children().last().find(".mejs-currenttime").length>0)f(this.options.timeAndDurationSeparator+'<span class="mejs-duration">'+(this.options.duration>0?mejs.Utility.secondsToTimeCode(this.options.duration,this.options.alwaysShowHours||this.media.duration>3600,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25):(a.options.alwaysShowHours?"00:":"")+(a.options.showTimecodeFrameCount?"00:00:00":"00:00"))+"</span>").appendTo(b.find(".mejs-time"));else{b.find(".mejs-currenttime").parent().addClass("mejs-currenttime-container"); +f('<div class="mejs-time mejs-duration-container"><span class="mejs-duration">'+(this.options.duration>0?mejs.Utility.secondsToTimeCode(this.options.duration,this.options.alwaysShowHours||this.media.duration>3600,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25):(a.options.alwaysShowHours?"00:":"")+(a.options.showTimecodeFrameCount?"00:00:00":"00:00"))+"</span></div>").appendTo(b)}this.durationD=this.controls.find(".mejs-duration");e.addEventListener("timeupdate",function(){a.updateDuration()}, +false)},updateCurrent:function(){if(this.currenttime)this.currenttime.html(mejs.Utility.secondsToTimeCode(this.media.currentTime,this.options.alwaysShowHours||this.media.duration>3600,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25))},updateDuration:function(){this.container.toggleClass("mejs-long-video",this.media.duration>3600);if(this.durationD&&(this.options.duration>0||this.media.duration))this.durationD.html(mejs.Utility.secondsToTimeCode(this.options.duration>0?this.options.duration: +this.media.duration,this.options.alwaysShowHours,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25))}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{muteText:mejs.i18n.t("Mute Toggle"),hideVolumeOnTouchDevices:true,audioVolume:"horizontal",videoVolume:"vertical"});f.extend(MediaElementPlayer.prototype,{buildvolume:function(a,b,c,e){if(!((mejs.MediaFeatures.isAndroid||mejs.MediaFeatures.isiOS)&&this.options.hideVolumeOnTouchDevices)){var d=this,g=d.isVideo?d.options.videoVolume:d.options.audioVolume,k=g=="horizontal"?f('<div class="mejs-button mejs-volume-button mejs-mute"><button type="button" aria-controls="'+ +d.id+'" title="'+d.options.muteText+'" aria-label="'+d.options.muteText+'"></button></div><div class="mejs-horizontal-volume-slider"><div class="mejs-horizontal-volume-total"></div><div class="mejs-horizontal-volume-current"></div><div class="mejs-horizontal-volume-handle"></div></div>').appendTo(b):f('<div class="mejs-button mejs-volume-button mejs-mute"><button type="button" aria-controls="'+d.id+'" title="'+d.options.muteText+'" aria-label="'+d.options.muteText+'"></button><div class="mejs-volume-slider"><div class="mejs-volume-total"></div><div class="mejs-volume-current"></div><div class="mejs-volume-handle"></div></div></div>').appendTo(b), +j=d.container.find(".mejs-volume-slider, .mejs-horizontal-volume-slider"),m=d.container.find(".mejs-volume-total, .mejs-horizontal-volume-total"),q=d.container.find(".mejs-volume-current, .mejs-horizontal-volume-current"),p=d.container.find(".mejs-volume-handle, .mejs-horizontal-volume-handle"),t=function(n,o){if(!j.is(":visible")&&typeof o=="undefined"){j.show();t(n,true);j.hide()}else{n=Math.max(0,n);n=Math.min(n,1);n==0?k.removeClass("mejs-mute").addClass("mejs-unmute"):k.removeClass("mejs-unmute").addClass("mejs-mute"); +if(g=="vertical"){var s=m.height(),u=m.position(),v=s-s*n;p.css("top",Math.round(u.top+v-p.height()/2));q.height(s-v);q.css("top",u.top+v)}else{s=m.width();u=m.position();s=s*n;p.css("left",Math.round(u.left+s-p.width()/2));q.width(Math.round(s))}}},h=function(n){var o=null,s=m.offset();if(g=="vertical"){o=m.height();parseInt(m.css("top").replace(/px/,""),10);o=(o-(n.pageY-s.top))/o;if(s.top==0||s.left==0)return}else{o=m.width();o=(n.pageX-s.left)/o}o=Math.max(0,o);o=Math.min(o,1);t(o);o==0?e.setMuted(true): +e.setMuted(false);e.setVolume(o)},l=false,r=false;k.hover(function(){j.show();r=true},function(){r=false;!l&&g=="vertical"&&j.hide()});j.bind("mouseover",function(){r=true}).bind("mousedown",function(n){h(n);d.globalBind("mousemove.vol",function(o){h(o)});d.globalBind("mouseup.vol",function(){l=false;d.globalUnbind(".vol");!r&&g=="vertical"&&j.hide()});l=true;return false});k.find("button").click(function(){e.setMuted(!e.muted)});e.addEventListener("volumechange",function(){if(!l)if(e.muted){t(0); +k.removeClass("mejs-mute").addClass("mejs-unmute")}else{t(e.volume);k.removeClass("mejs-unmute").addClass("mejs-mute")}},false);if(d.container.is(":visible")){t(a.options.startVolume);a.options.startVolume===0&&e.setMuted(true);e.pluginType==="native"&&e.setVolume(a.options.startVolume)}}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{usePluginFullScreen:true,newWindowCallback:function(){return""},fullscreenText:mejs.i18n.t("Fullscreen")});f.extend(MediaElementPlayer.prototype,{isFullScreen:false,isNativeFullScreen:false,isInIframe:false,buildfullscreen:function(a,b,c,e){if(a.isVideo){a.isInIframe=window.location!=window.parent.location;mejs.MediaFeatures.hasTrueNativeFullScreen&&a.globalBind(mejs.MediaFeatures.fullScreenEventName,function(){if(a.isFullScreen)if(mejs.MediaFeatures.isFullScreen()){a.isNativeFullScreen= +true;a.setControlsSize()}else{a.isNativeFullScreen=false;a.exitFullScreen()}});var d=this,g=f('<div class="mejs-button mejs-fullscreen-button"><button type="button" aria-controls="'+d.id+'" title="'+d.options.fullscreenText+'" aria-label="'+d.options.fullscreenText+'"></button></div>').appendTo(b);if(d.media.pluginType==="native"||!d.options.usePluginFullScreen&&!mejs.MediaFeatures.isFirefox)g.click(function(){mejs.MediaFeatures.hasTrueNativeFullScreen&&mejs.MediaFeatures.isFullScreen()||a.isFullScreen? +a.exitFullScreen():a.enterFullScreen()});else{var k=null;if(function(){var h=document.createElement("x"),l=document.documentElement,r=window.getComputedStyle;if(!("pointerEvents"in h.style))return false;h.style.pointerEvents="auto";h.style.pointerEvents="x";l.appendChild(h);r=r&&r(h,"").pointerEvents==="auto";l.removeChild(h);return!!r}()&&!mejs.MediaFeatures.isOpera){var j=false,m=function(){if(j){for(var h in q)q[h].hide();g.css("pointer-events","");d.controls.css("pointer-events","");d.media.removeEventListener("click", +d.clickToPlayPauseCallback);j=false}},q={};b=["top","left","right","bottom"];var p,t=function(){var h=g.offset().left-d.container.offset().left,l=g.offset().top-d.container.offset().top,r=g.outerWidth(true),n=g.outerHeight(true),o=d.container.width(),s=d.container.height();for(p in q)q[p].css({position:"absolute",top:0,left:0});q.top.width(o).height(l);q.left.width(h).height(n).css({top:l});q.right.width(o-h-r).height(n).css({top:l,left:h+r});q.bottom.width(o).height(s-n-l).css({top:l+n})};d.globalBind("resize", +function(){t()});p=0;for(c=b.length;p<c;p++)q[b[p]]=f('<div class="mejs-fullscreen-hover" />').appendTo(d.container).mouseover(m).hide();g.on("mouseover",function(){if(!d.isFullScreen){var h=g.offset(),l=a.container.offset();e.positionFullscreenButton(h.left-l.left,h.top-l.top,false);g.css("pointer-events","none");d.controls.css("pointer-events","none");d.media.addEventListener("click",d.clickToPlayPauseCallback);for(p in q)q[p].show();t();j=true}});e.addEventListener("fullscreenchange",function(){d.isFullScreen= +!d.isFullScreen;d.isFullScreen?d.media.removeEventListener("click",d.clickToPlayPauseCallback):d.media.addEventListener("click",d.clickToPlayPauseCallback);m()});d.globalBind("mousemove",function(h){if(j){var l=g.offset();if(h.pageY<l.top||h.pageY>l.top+g.outerHeight(true)||h.pageX<l.left||h.pageX>l.left+g.outerWidth(true)){g.css("pointer-events","");d.controls.css("pointer-events","");j=false}}})}else g.on("mouseover",function(){if(k!==null){clearTimeout(k);delete k}var h=g.offset(),l=a.container.offset(); +e.positionFullscreenButton(h.left-l.left,h.top-l.top,true)}).on("mouseout",function(){if(k!==null){clearTimeout(k);delete k}k=setTimeout(function(){e.hideFullscreenButton()},1500)})}a.fullscreenBtn=g;d.globalBind("keydown",function(h){if((mejs.MediaFeatures.hasTrueNativeFullScreen&&mejs.MediaFeatures.isFullScreen()||d.isFullScreen)&&h.keyCode==27)a.exitFullScreen()})}},cleanfullscreen:function(a){a.exitFullScreen()},containerSizeTimeout:null,enterFullScreen:function(){var a=this;if(!(a.media.pluginType!== +"native"&&(mejs.MediaFeatures.isFirefox||a.options.usePluginFullScreen))){f(document.documentElement).addClass("mejs-fullscreen");normalHeight=a.container.height();normalWidth=a.container.width();if(a.media.pluginType==="native")if(mejs.MediaFeatures.hasTrueNativeFullScreen){mejs.MediaFeatures.requestFullScreen(a.container[0]);a.isInIframe&&setTimeout(function c(){if(a.isNativeFullScreen){var e=(window.devicePixelRatio||1)*f(window).width(),d=screen.width;Math.abs(d-e)>d*0.0020?a.exitFullScreen(): +setTimeout(c,500)}},500)}else if(mejs.MediaFeatures.hasSemiNativeFullScreen){a.media.webkitEnterFullscreen();return}if(a.isInIframe){var b=a.options.newWindowCallback(this);if(b!=="")if(mejs.MediaFeatures.hasTrueNativeFullScreen)setTimeout(function(){if(!a.isNativeFullScreen){a.pause();window.open(b,a.id,"top=0,left=0,width="+screen.availWidth+",height="+screen.availHeight+",resizable=yes,scrollbars=no,status=no,toolbar=no")}},250);else{a.pause();window.open(b,a.id,"top=0,left=0,width="+screen.availWidth+ +",height="+screen.availHeight+",resizable=yes,scrollbars=no,status=no,toolbar=no");return}}a.container.addClass("mejs-container-fullscreen").width("100%").height("100%");a.containerSizeTimeout=setTimeout(function(){a.container.css({width:"100%",height:"100%"});a.setControlsSize()},500);if(a.media.pluginType==="native")a.$media.width("100%").height("100%");else{a.container.find(".mejs-shim").width("100%").height("100%");a.media.setVideoSize(f(window).width(),f(window).height())}a.layers.children("div").width("100%").height("100%"); +a.fullscreenBtn&&a.fullscreenBtn.removeClass("mejs-fullscreen").addClass("mejs-unfullscreen");a.setControlsSize();a.isFullScreen=true;a.container.find(".mejs-captions-text").css("font-size",screen.width/a.width*1*100+"%");a.container.find(".mejs-captions-position").css("bottom","45px")}},exitFullScreen:function(){clearTimeout(this.containerSizeTimeout);if(this.media.pluginType!=="native"&&mejs.MediaFeatures.isFirefox)this.media.setFullscreen(false);else{if(mejs.MediaFeatures.hasTrueNativeFullScreen&& +(mejs.MediaFeatures.isFullScreen()||this.isFullScreen))mejs.MediaFeatures.cancelFullScreen();f(document.documentElement).removeClass("mejs-fullscreen");this.container.removeClass("mejs-container-fullscreen").width(normalWidth).height(normalHeight);if(this.media.pluginType==="native")this.$media.width(normalWidth).height(normalHeight);else{this.container.find(".mejs-shim").width(normalWidth).height(normalHeight);this.media.setVideoSize(normalWidth,normalHeight)}this.layers.children("div").width(normalWidth).height(normalHeight); +this.fullscreenBtn.removeClass("mejs-unfullscreen").addClass("mejs-fullscreen");this.setControlsSize();this.isFullScreen=false;this.container.find(".mejs-captions-text").css("font-size","");this.container.find(".mejs-captions-position").css("bottom","")}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{speeds:["1.50","1.25","1.00","0.75"],defaultSpeed:"1.00"});f.extend(MediaElementPlayer.prototype,{buildspeed:function(a,b,c,e){if(this.media.pluginType=="native"){c='<div class="mejs-button mejs-speed-button"><button type="button">'+this.options.defaultSpeed+'x</button><div class="mejs-speed-selector"><ul>';var d;f.inArray(this.options.defaultSpeed,this.options.speeds)===-1&&this.options.speeds.push(this.options.defaultSpeed);this.options.speeds.sort(function(g, +k){return parseFloat(k)-parseFloat(g)});for(d=0;d<this.options.speeds.length;d++){c+='<li><input type="radio" name="speed" value="'+this.options.speeds[d]+'" id="'+this.options.speeds[d]+'" ';if(this.options.speeds[d]==this.options.defaultSpeed){c+="checked=true ";c+='/><label for="'+this.options.speeds[d]+'" class="mejs-speed-selected">'+this.options.speeds[d]+"x</label></li>"}else c+='/><label for="'+this.options.speeds[d]+'">'+this.options.speeds[d]+"x</label></li>"}c+="</ul></div></div>";a.speedButton= +f(c).appendTo(b);a.playbackspeed=this.options.defaultSpeed;a.speedButton.on("click","input[type=radio]",function(){a.playbackspeed=f(this).attr("value");e.playbackRate=parseFloat(a.playbackspeed);a.speedButton.find("button").text(a.playbackspeed+"x");a.speedButton.find(".mejs-speed-selected").removeClass("mejs-speed-selected");a.speedButton.find("input[type=radio]:checked").next().addClass("mejs-speed-selected")});b=a.speedButton.find(".mejs-speed-selector");b.height(this.speedButton.find(".mejs-speed-selector ul").outerHeight(true)+ +a.speedButton.find(".mejs-speed-translations").outerHeight(true));b.css("top",-1*b.height()+"px")}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{startLanguage:"",tracksText:mejs.i18n.t("Captions/Subtitles"),hideCaptionsButtonWhenEmpty:true,toggleCaptionsButtonWhenOnlyOne:false,slidesSelector:""});f.extend(MediaElementPlayer.prototype,{hasChapters:false,buildtracks:function(a,b,c,e){if(a.tracks.length!==0){var d;if(this.domNode.textTracks)for(d=this.domNode.textTracks.length-1;d>=0;d--)this.domNode.textTracks[d].mode="hidden";a.chapters=f('<div class="mejs-chapters mejs-layer"></div>').prependTo(c).hide(); +a.captions=f('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover"><span class="mejs-captions-text"></span></div></div>').prependTo(c).hide();a.captionsText=a.captions.find(".mejs-captions-text");a.captionsButton=f('<div class="mejs-button mejs-captions-button"><button type="button" aria-controls="'+this.id+'" title="'+this.options.tracksText+'" aria-label="'+this.options.tracksText+'"></button><div class="mejs-captions-selector"><ul><li><input type="radio" name="'+ +a.id+'_captions" id="'+a.id+'_captions_none" value="none" checked="checked" /><label for="'+a.id+'_captions_none">'+mejs.i18n.t("None")+"</label></li></ul></div></div>").appendTo(b);for(d=b=0;d<a.tracks.length;d++)a.tracks[d].kind=="subtitles"&&b++;if(this.options.toggleCaptionsButtonWhenOnlyOne&&b==1)a.captionsButton.on("click",function(){lang=a.selectedTrack===null?a.tracks[0].srclang:"none";a.setTrack(lang)});else{a.captionsButton.on("mouseenter focusin",function(){f(this).find(".mejs-captions-selector").css("visibility", +"visible")}).on("click","input[type=radio]",function(){lang=this.value;a.setTrack(lang)});a.captionsButton.on("mouseleave focusout",function(){f(this).find(".mejs-captions-selector").css("visibility","hidden")})}a.options.alwaysShowControls?a.container.find(".mejs-captions-position").addClass("mejs-captions-position-hover"):a.container.bind("controlsshown",function(){a.container.find(".mejs-captions-position").addClass("mejs-captions-position-hover")}).bind("controlshidden",function(){e.paused||a.container.find(".mejs-captions-position").removeClass("mejs-captions-position-hover")}); +a.trackToLoad=-1;a.selectedTrack=null;a.isLoadingTrack=false;for(d=0;d<a.tracks.length;d++)a.tracks[d].kind=="subtitles"&&a.addTrackButton(a.tracks[d].srclang,a.tracks[d].label);a.loadNextTrack();e.addEventListener("timeupdate",function(){a.displayCaptions()},false);if(a.options.slidesSelector!==""){a.slidesContainer=f(a.options.slidesSelector);e.addEventListener("timeupdate",function(){a.displaySlides()},false)}e.addEventListener("loadedmetadata",function(){a.displayChapters()},false);a.container.hover(function(){if(a.hasChapters){a.chapters.css("visibility", +"visible");a.chapters.fadeIn(200).height(a.chapters.find(".mejs-chapter").outerHeight())}},function(){a.hasChapters&&!e.paused&&a.chapters.fadeOut(200,function(){f(this).css("visibility","hidden");f(this).css("display","block")})});a.node.getAttribute("autoplay")!==null&&a.chapters.css("visibility","hidden")}},setTrack:function(a){var b;if(a=="none"){this.selectedTrack=null;this.captionsButton.removeClass("mejs-captions-enabled")}else for(b=0;b<this.tracks.length;b++)if(this.tracks[b].srclang==a){this.selectedTrack=== +null&&this.captionsButton.addClass("mejs-captions-enabled");this.selectedTrack=this.tracks[b];this.captions.attr("lang",this.selectedTrack.srclang);this.displayCaptions();break}},loadNextTrack:function(){this.trackToLoad++;if(this.trackToLoad<this.tracks.length){this.isLoadingTrack=true;this.loadTrack(this.trackToLoad)}else{this.isLoadingTrack=false;this.checkForTracks()}},loadTrack:function(a){var b=this,c=b.tracks[a];f.ajax({url:c.src,dataType:"text",success:function(e){c.entries=typeof e=="string"&& +/<tt\s+xml/ig.exec(e)?mejs.TrackFormatParser.dfxp.parse(e):mejs.TrackFormatParser.webvtt.parse(e);c.isLoaded=true;b.enableTrackButton(c.srclang,c.label);b.loadNextTrack();c.kind=="chapters"&&b.media.addEventListener("play",function(){b.media.duration>0&&b.displayChapters(c)},false);c.kind=="slides"&&b.setupSlides(c)},error:function(){b.loadNextTrack()}})},enableTrackButton:function(a,b){if(b==="")b=mejs.language.codes[a]||a;this.captionsButton.find("input[value="+a+"]").prop("disabled",false).siblings("label").html(b); +this.options.startLanguage==a&&f("#"+this.id+"_captions_"+a).prop("checked",true).trigger("click");this.adjustLanguageBox()},addTrackButton:function(a,b){if(b==="")b=mejs.language.codes[a]||a;this.captionsButton.find("ul").append(f('<li><input type="radio" name="'+this.id+'_captions" id="'+this.id+"_captions_"+a+'" value="'+a+'" disabled="disabled" /><label for="'+this.id+"_captions_"+a+'">'+b+" (loading)</label></li>"));this.adjustLanguageBox();this.container.find(".mejs-captions-translations option[value="+ +a+"]").remove()},adjustLanguageBox:function(){this.captionsButton.find(".mejs-captions-selector").height(this.captionsButton.find(".mejs-captions-selector ul").outerHeight(true)+this.captionsButton.find(".mejs-captions-translations").outerHeight(true))},checkForTracks:function(){var a=false;if(this.options.hideCaptionsButtonWhenEmpty){for(i=0;i<this.tracks.length;i++)if(this.tracks[i].kind=="subtitles"){a=true;break}if(!a){this.captionsButton.hide();this.setControlsSize()}}},displayCaptions:function(){if(typeof this.tracks!= +"undefined"){var a,b=this.selectedTrack;if(b!==null&&b.isLoaded)for(a=0;a<b.entries.times.length;a++)if(this.media.currentTime>=b.entries.times[a].start&&this.media.currentTime<=b.entries.times[a].stop){this.captionsText.html(b.entries.text[a]).attr("class","mejs-captions-text "+(b.entries.times[a].identifier||""));this.captions.show().height(0);return}this.captions.hide()}},setupSlides:function(a){this.slides=a;this.slides.entries.imgs=[this.slides.entries.text.length];this.showSlide(0)},showSlide:function(a){if(!(typeof this.tracks== +"undefined"||typeof this.slidesContainer=="undefined")){var b=this,c=b.slides.entries.text[a],e=b.slides.entries.imgs[a];if(typeof e=="undefined"||typeof e.fadeIn=="undefined")b.slides.entries.imgs[a]=e=f('<img src="'+c+'">').on("load",function(){e.appendTo(b.slidesContainer).hide().fadeIn().siblings(":visible").fadeOut()});else!e.is(":visible")&&!e.is(":animated")&&e.fadeIn().siblings(":visible").fadeOut()}},displaySlides:function(){if(typeof this.slides!="undefined"){var a=this.slides,b;for(b=0;b< +a.entries.times.length;b++)if(this.media.currentTime>=a.entries.times[b].start&&this.media.currentTime<=a.entries.times[b].stop){this.showSlide(b);break}}},displayChapters:function(){var a;for(a=0;a<this.tracks.length;a++)if(this.tracks[a].kind=="chapters"&&this.tracks[a].isLoaded){this.drawChapters(this.tracks[a]);this.hasChapters=true;break}},drawChapters:function(a){var b=this,c,e,d=e=0;b.chapters.empty();for(c=0;c<a.entries.times.length;c++){e=a.entries.times[c].stop-a.entries.times[c].start; +e=Math.floor(e/b.media.duration*100);if(e+d>100||c==a.entries.times.length-1&&e+d<100)e=100-d;b.chapters.append(f('<div class="mejs-chapter" rel="'+a.entries.times[c].start+'" style="left: '+d.toString()+"%;width: "+e.toString()+'%;"><div class="mejs-chapter-block'+(c==a.entries.times.length-1?" mejs-chapter-block-last":"")+'"><span class="ch-title">'+a.entries.text[c]+'</span><span class="ch-time">'+mejs.Utility.secondsToTimeCode(a.entries.times[c].start)+"–"+mejs.Utility.secondsToTimeCode(a.entries.times[c].stop)+ +"</span></div></div>"));d+=e}b.chapters.find("div.mejs-chapter").click(function(){b.media.setCurrentTime(parseFloat(f(this).attr("rel")));b.media.paused&&b.media.play()});b.chapters.show()}});mejs.language={codes:{af:"Afrikaans",sq:"Albanian",ar:"Arabic",be:"Belarusian",bg:"Bulgarian",ca:"Catalan",zh:"Chinese","zh-cn":"Chinese Simplified","zh-tw":"Chinese Traditional",hr:"Croatian",cs:"Czech",da:"Danish",nl:"Dutch",en:"English",et:"Estonian",fl:"Filipino",fi:"Finnish",fr:"French",gl:"Galician",de:"German", +el:"Greek",ht:"Haitian Creole",iw:"Hebrew",hi:"Hindi",hu:"Hungarian",is:"Icelandic",id:"Indonesian",ga:"Irish",it:"Italian",ja:"Japanese",ko:"Korean",lv:"Latvian",lt:"Lithuanian",mk:"Macedonian",ms:"Malay",mt:"Maltese",no:"Norwegian",fa:"Persian",pl:"Polish",pt:"Portuguese",ro:"Romanian",ru:"Russian",sr:"Serbian",sk:"Slovak",sl:"Slovenian",es:"Spanish",sw:"Swahili",sv:"Swedish",tl:"Tagalog",th:"Thai",tr:"Turkish",uk:"Ukrainian",vi:"Vietnamese",cy:"Welsh",yi:"Yiddish"}};mejs.TrackFormatParser={webvtt:{pattern_timecode:/^((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, +parse:function(a){var b=0;a=mejs.TrackFormatParser.split2(a,/\r?\n/);for(var c={text:[],times:[]},e,d,g;b<a.length;b++){if((e=this.pattern_timecode.exec(a[b]))&&b<a.length){if(b-1>=0&&a[b-1]!=="")g=a[b-1];b++;d=a[b];for(b++;a[b]!==""&&b<a.length;){d=d+"\n"+a[b];b++}d=f.trim(d).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig,"<a href='$1' target='_blank'>$1</a>");c.text.push(d);c.times.push({identifier:g,start:mejs.Utility.convertSMPTEtoSeconds(e[1])===0?0.2:mejs.Utility.convertSMPTEtoSeconds(e[1]), +stop:mejs.Utility.convertSMPTEtoSeconds(e[3]),settings:e[5]})}g=""}return c}},dfxp:{parse:function(a){a=f(a).filter("tt");var b=0;b=a.children("div").eq(0);var c=b.find("p");b=a.find("#"+b.attr("style"));var e,d;a={text:[],times:[]};if(b.length){d=b.removeAttr("id").get(0).attributes;if(d.length){e={};for(b=0;b<d.length;b++)e[d[b].name.split(":")[1]]=d[b].value}}for(b=0;b<c.length;b++){var g;d={start:null,stop:null,style:null};if(c.eq(b).attr("begin"))d.start=mejs.Utility.convertSMPTEtoSeconds(c.eq(b).attr("begin")); +if(!d.start&&c.eq(b-1).attr("end"))d.start=mejs.Utility.convertSMPTEtoSeconds(c.eq(b-1).attr("end"));if(c.eq(b).attr("end"))d.stop=mejs.Utility.convertSMPTEtoSeconds(c.eq(b).attr("end"));if(!d.stop&&c.eq(b+1).attr("begin"))d.stop=mejs.Utility.convertSMPTEtoSeconds(c.eq(b+1).attr("begin"));if(e){g="";for(var k in e)g+=k+":"+e[k]+";"}if(g)d.style=g;if(d.start===0)d.start=0.2;a.times.push(d);d=f.trim(c.eq(b).html()).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, +"<a href='$1' target='_blank'>$1</a>");a.text.push(d);if(a.times.start===0)a.times.start=2}return a}},split2:function(a,b){return a.split(b)}};if("x\n\ny".split(/\n/gi).length!=3)mejs.TrackFormatParser.split2=function(a,b){var c=[],e="",d;for(d=0;d<a.length;d++){e+=a.substring(d,d+1);if(b.test(e)){c.push(e.replace(b,""));e=""}}c.push(e);return c}})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{contextMenuItems:[{render:function(a){if(typeof a.enterFullScreen=="undefined")return null;return a.isFullScreen?mejs.i18n.t("Turn off Fullscreen"):mejs.i18n.t("Go Fullscreen")},click:function(a){a.isFullScreen?a.exitFullScreen():a.enterFullScreen()}},{render:function(a){return a.media.muted?mejs.i18n.t("Unmute"):mejs.i18n.t("Mute")},click:function(a){a.media.muted?a.setMuted(false):a.setMuted(true)}},{isSeparator:true},{render:function(){return mejs.i18n.t("Download Video")}, +click:function(a){window.location.href=a.media.currentSrc}}]});f.extend(MediaElementPlayer.prototype,{buildcontextmenu:function(a){a.contextMenu=f('<div class="mejs-contextmenu"></div>').appendTo(f("body")).hide();a.container.bind("contextmenu",function(b){if(a.isContextMenuEnabled){b.preventDefault();a.renderContextMenu(b.clientX-1,b.clientY-1);return false}});a.container.bind("click",function(){a.contextMenu.hide()});a.contextMenu.bind("mouseleave",function(){a.startContextMenuTimer()})},cleancontextmenu:function(a){a.contextMenu.remove()}, +isContextMenuEnabled:true,enableContextMenu:function(){this.isContextMenuEnabled=true},disableContextMenu:function(){this.isContextMenuEnabled=false},contextMenuTimeout:null,startContextMenuTimer:function(){var a=this;a.killContextMenuTimer();a.contextMenuTimer=setTimeout(function(){a.hideContextMenu();a.killContextMenuTimer()},750)},killContextMenuTimer:function(){var a=this.contextMenuTimer;if(a!=null){clearTimeout(a);delete a}},hideContextMenu:function(){this.contextMenu.hide()},renderContextMenu:function(a, +b){for(var c=this,e="",d=c.options.contextMenuItems,g=0,k=d.length;g<k;g++)if(d[g].isSeparator)e+='<div class="mejs-contextmenu-separator"></div>';else{var j=d[g].render(c);if(j!=null)e+='<div class="mejs-contextmenu-item" data-itemindex="'+g+'" id="element-'+Math.random()*1E6+'">'+j+"</div>"}c.contextMenu.empty().append(f(e)).css({top:b,left:a}).show();c.contextMenu.find(".mejs-contextmenu-item").each(function(){var m=f(this),q=parseInt(m.data("itemindex"),10),p=c.options.contextMenuItems[q];typeof p.show!= +"undefined"&&p.show(m,c);m.click(function(){typeof p.click!="undefined"&&p.click(c);c.contextMenu.hide()})});setTimeout(function(){c.killControlsTimer("rev3")},100)}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{postrollCloseText:mejs.i18n.t("Close")});f.extend(MediaElementPlayer.prototype,{buildpostroll:function(a,b,c){var e=this.container.find('link[rel="postroll"]').attr("href");if(typeof e!=="undefined"){a.postroll=f('<div class="mejs-postroll-layer mejs-layer"><a class="mejs-postroll-close" onclick="$(this).parent().hide();return false;">'+this.options.postrollCloseText+'</a><div class="mejs-postroll-layer-content"></div></div>').prependTo(c).hide();this.media.addEventListener("ended", +function(){f.ajax({dataType:"html",url:e,success:function(d){c.find(".mejs-postroll-layer-content").html(d)}});a.postroll.show()},false)}}})})(mejs.$); diff --git a/js/mediaelement/build/mejs-skins.css b/js/mediaelement/build/mejs-skins.css new file mode 100644 index 0000000000000000000000000000000000000000..5c27cf156fcd04effddbd141aae1b034b6eab8ff --- /dev/null +++ b/js/mediaelement/build/mejs-skins.css @@ -0,0 +1,289 @@ +/* TED player */ +.mejs-container.mejs-ted { + +} +.mejs-ted .mejs-controls { + background: #eee; + height: 65px; +} + +.mejs-ted .mejs-button, +.mejs-ted .mejs-time { + position: absolute; + background: #ddd; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-total { + background-color: none; + background: url(controls-ted.png) repeat-x 0 -52px; + height: 6px; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-buffering { + height: 6px; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-loaded { + background-color: none; + background: url(controls-ted.png) repeat-x 0 -52px; + width: 0; + height: 6px; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-current { + width: 0; + height: 6px; + background-color: none; + background: url(controls-ted.png) repeat-x 0 -59px; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-handle { + display: block; + margin: 0; + width: 14px; + height: 21px; + top: -7px; + border: 0; + background: url(controls-ted.png) no-repeat 0 0; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-float { + display: none; +} +.mejs-ted .mejs-controls .mejs-playpause-button { + top: 29px; + left: 9px; + width: 49px; + height: 28px; +} +.mejs-ted .mejs-controls .mejs-playpause-button button { + width: 49px; + height: 28px; + background: url(controls-ted.png) no-repeat -50px -23px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-pause button { + background-position: 0 -23px; +} + +.mejs-ted .mejs-controls .mejs-fullscreen-button { + top: 34px; + right: 9px; + width: 17px; + height: 15px; + background : none; +} +.mejs-ted .mejs-controls .mejs-fullscreen-button button { + width: 19px; + height: 17px; + background: transparent url(controls-ted.png) no-repeat 0 -66px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-unfullscreen button { + background: transparent url(controls-ted.png) no-repeat -21px -66px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-volume-button { + top: 30px; + right: 35px; + width: 24px; + height: 22px; +} +.mejs-ted .mejs-controls .mejs-mute button { + background: url(controls-ted.png) no-repeat -15px 0; + width: 24px; + height: 22px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-unmute button { + background: url(controls-ted.png) no-repeat -40px 0; + width: 24px; + height: 22px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-volume-button .mejs-volume-slider { + background: #fff; + border: solid 1px #aaa; + border-width: 1px 1px 0 1px; + width: 22px; + height: 65px; + top: -65px; +} +.mejs-ted .mejs-controls .mejs-volume-button .mejs-volume-total { + background: url(controls-ted.png) repeat-y -41px -66px; + left: 8px; + width: 6px; + height: 50px; +} +.mejs-ted .mejs-controls .mejs-volume-button .mejs-volume-current { + left: 8px; + width: 6px; + background: url(controls-ted.png) repeat-y -48px -66px; + height: 50px; +} + +.mejs-ted .mejs-controls .mejs-volume-button .mejs-volume-handle { + display: none; +} + +.mejs-ted .mejs-controls .mejs-time span { + color: #333; +} +.mejs-ted .mejs-controls .mejs-currenttime-container { + position: absolute; + top: 32px; + right: 100px; + border: solid 1px #999; + background: #fff; + color: #333; + padding-top: 2px; + border-radius: 3px; + color: #333; +} +.mejs-ted .mejs-controls .mejs-duration-container { + + position: absolute; + top: 32px; + right: 65px; + border: solid 1px #999; + background: #fff; + color: #333; + padding-top: 2px; + border-radius: 3px; + color: #333; +} + +.mejs-ted .mejs-controls .mejs-time button{ + color: #333; +} +.mejs-ted .mejs-controls .mejs-captions-button { + display: none; +} +/* END: TED player */ + + +/* WMP player */ +.mejs-container.mejs-wmp { + +} +.mejs-wmp .mejs-controls { + background: transparent url(controls-wmp-bg.png) center 16px no-repeat; + height: 65px; +} + +.mejs-wmp .mejs-button, +.mejs-wmp .mejs-time { + position: absolute; + background: transparent; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-total { + background-color: transparent; + border: solid 1px #ccc; + height: 3px; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-buffering { + height: 3px; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-loaded { + background-color: rgba(255,255,255,0.3); + width: 0; + height: 3px; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-current { + width: 0; + height: 1px; + background-color: #014CB6; + border: solid 1px #7FC9FA; + border-width: 1px 0; + border-color: #7FC9FA #fff #619FF2 #fff; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-handle { + display: block; + margin: 0; + width: 16px; + height: 9px; + top: -3px; + border: 0; + background: url(controls-wmp.png) no-repeat 0 -80px; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-float { + display: none; +} +.mejs-wmp .mejs-controls .mejs-playpause-button { + top: 10px; + left: 50%; + margin: 10px 0 0 -20px; + width: 40px; + height: 40px; + +} +.mejs-wmp .mejs-controls .mejs-playpause-button button { + width: 40px; + height: 40px; + background: url(controls-wmp.png) no-repeat 0 0; + margin: 0; + padding: 0; +} +.mejs-wmp .mejs-controls .mejs-pause button { + background-position: 0 -40px; +} + +.mejs-wmp .mejs-controls .mejs-currenttime-container { + position: absolute; + top: 25px; + left: 50%; + margin-left: -93px; +} +.mejs-wmp .mejs-controls .mejs-duration-container { + position: absolute; + top: 25px; + left: 50%; + margin-left: -58px; +} + + +.mejs-wmp .mejs-controls .mejs-volume-button { + top: 32px; + right: 50%; + margin-right: -55px; + width: 20px; + height: 15px; +} +.mejs-wmp .mejs-controls .mejs-volume-button button { + margin: 0; + padding: 0; + background: url(controls-wmp.png) no-repeat -42px -17px; + width: 20px; + height: 15px; +} +.mejs-wmp .mejs-controls .mejs-unmute button { + margin: 0; + padding: 0; + background: url(controls-wmp.png) no-repeat -42px 0; + width: 20px; + height: 15px; +} +.mejs-wmp .mejs-controls .mejs-volume-button .mejs-volume-slider { + background: rgba(102,102,102,0.6); +} + +.mejs-wmp .mejs-controls .mejs-fullscreen-button { + top: 32px; + right: 50%; + margin-right: -82px; + width: 15px; + height: 14px; +} +.mejs-wmp .mejs-controls .mejs-fullscreen-button button { + margin: 0; + padding: 0; + background: url(controls-wmp.png) no-repeat -63px 0; + width: 15px; + height: 14px; +} +.mejs-wmp .mejs-controls .mejs-captions-button { + display: none; +} +/* END: WMP player */ + + + diff --git a/js/mediaelement/build/silverlightmediaelement.xap b/js/mediaelement/build/silverlightmediaelement.xap new file mode 100644 index 0000000000000000000000000000000000000000..9d55c2e46ae51017ab91f61b05cf4f4838ab10ab Binary files /dev/null and b/js/mediaelement/build/silverlightmediaelement.xap differ diff --git a/js/mediaelement/changelog.md b/js/mediaelement/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..3ae6b68875e288ae55fe91281283beb6d13d6c6f --- /dev/null +++ b/js/mediaelement/changelog.md @@ -0,0 +1,716 @@ +### Version History + +*2.15.2 (2014/09/??) - in progress * + +* Migration from Builder.py to Grunt (https://github.com/johndyer/mediaelement/pull/1309) [dmfrancisco] +* remove event listeners from flash AudioElement (https://github.com/johndyer/mediaelement/pull/1294) [phinze] +* mep-feature-ads: Link overlay only when url is given (https://github.com/johndyer/mediaelement/pull/1296) [schrolli] +* Add repository field to package.json to avoid warning (https://github.com/johndyer/mediaelement/pull/1300) [dmfrancisco] +* Fix IE8 error on window resizing (https://github.com/johndyer/mediaelement/pull/1301) [chemerisuk] +* send `this` to dispatchEvent callbacks (https://github.com/johndyer/mediaelement/pull/1295) [phinze] +* Added link to MIT license to README.md (https://github.com/johndyer/mediaelement/pull/1303) [SuriyaaKudoIsc] + + +*2.15.1 (2014/08/11)* + +* Fixes for various sizing issues with 2.15.0 +* Fix nativeWidth() to return defaultAudioWidth (https://github.com/johndyer/mediaelement/pull/1271) +* Don't set .me-plugin width/height to 0 (https://github.com/johndyer/mediaelement/pull/1273) [staylor] +* Remove video-only restriction on playback speed feature (https://github.com/johndyer/mediaelement/pull/1268/files) [phinze] +* a work around for zero height containers (https://github.com/johndyer/mediaelement/pull/1274) [maimairel] + + +*2.15.0 (2014/08/03)* + +* support m3u8 (https://github.com/johndyer/mediaelement/pull/1074) [clkao] +* add Flash/HTTP Live Streaming Support (https://github.com/johndyer/mediaelement/pull/1066) [mangui] +* Fix jQuery reference in plugin (https://github.com/johndyer/mediaelement/pull/1231) [blackbyte-pl] +* Added plugin for Universal Google Analytics (https://github.com/johndyer/mediaelement/pull/1229) [louiedp3] +* Implement keyboard accessibility (https://github.com/johndyer/mediaelement/pull/1221) [joedolson] +* Removed "touchstart" to prevent Android issues (https://github.com/johndyer/mediaelement/pull/1212) [MoritzGiessmann] +* Vimeo API Cleanup and Fix (https://github.com/johndyer/mediaelement/pull/1199) [maimairel] +* Fix for silverlight issue with fullscreen button (https://github.com/johndyer/mediaelement/pull/1196) [PaulVrugt] +* Fix IE10/11 error with `clone()` and `show()` (https://github.com/johndyer/mediaelement/pull/1194) [benjroy] +* Add bower.json (https://github.com/johndyer/mediaelement/pull/1188) [herby] +* i18n : add French translation (https://github.com/johndyer/mediaelement/pull/1177) [kloh-fr] +* IE8 layout (Adding width and height to .me-plugin) [peterh-capella] +* Fixes #1113 - Youtube Playbutton on load hidden (https://github.com/johndyer/mediaelement/pull/1140) [LOK-Soft] +* setVideoSize : verify this.pluginElement before this.pluginElement.style (https://github.com/johndyer/mediaelement/pull/1175) [bdecarne] +* VideoElement.as: fix duration on setSrc (https://github.com/johndyer/mediaelement/pull/1127) [rounce] +* don't hide controls when they're being hovered over (https://github.com/johndyer/mediaelement/pull/1121/files) [rounce] +* flash: send keydown events up to javascript (https://github.com/johndyer/mediaelement/pull/1120) [rounce] +* Restore IE6 compatibility for 100% (https://github.com/johndyer/mediaelement/pull/1021) [ryokenau] +* Added playback speed (HTML5 only), fixed caption's auto-size when fullscreen (https://github.com/johndyer/mediaelement/pull/1027) [cheng-shiwen] +* Cleaned up playback speed (https://github.com/johndyer/mediaelement/pull/1249) [matthillman] +* Chromium Web Browser Support (https://github.com/johndyer/mediaelement/pull/1020) [ryokenau] +* Always listen for the fullscreenchange event on the document (https://github.com/johndyer/mediaelement/pull/1240) [foolip ] +* The hours are not required on the webvtt format (https://github.com/johndyer/mediaelement/pull/1252) [LeResKP] +* Fix wrong initial player size when responsive (https://github.com/johndyer/mediaelement/pull/1247) [Wizard13] +* Make mejs.MediaFeatures.isFullScreen() more consistent (https://github.com/johndyer/mediaelement/pull/1239) [foolip] +* Fix flash source chooser (https://github.com/johndyer/mediaelement/pull/1191) [dajulia3] +* Option `setDimensions` to allow deactivation of inline widths and heights at player elements (https://github.com/johndyer/mediaelement/pull/1236) [feeela] +* Fix Captions start language is not ticked in Firefox (https://github.com/johndyer/mediaelement/pull/1260) +* Updated SVG to fix Chrome 38's rendering problems + +*2.14.2 (2014/04/04)* + +* Additional progress bar checks for hidden/missing bars +* Add Gruntfile.js build support (https://github.com/johndyer/mediaelement/pull/1147) [jeremyfelt] +* Add #! line to Builder.py for legacy builds (https://github.com/johndyer/mediaelement/pull/1036) [amenonsen] + +*2.14.1 (2014/03/31)* + +* Fix infinite loop on progress bar + +*2.14.0 (2014/03/29)* + +* Vimeo support (https://github.com/johndyer/mediaelement/pull/1079) [clkao] +* fix for aac-audio (itunes-samples etc.) (https://github.com/johndyer/mediaelement/pull/1133) [faebser] +* added 'm4a' file type, to be detected as 'audio/mp4' (https://github.com/johndyer/mediaelement/pull/988) [heshiming] +* Function remove() should remove mejs container only if it exists (https://github.com/johndyer/mediaelement/pull/1144) [lucash] +* Handle the case when parentNode is null (https://github.com/johndyer/mediaelement/pull/1136) [lbeder, also hypomodern] +* fix leaky variables (https://github.com/johndyer/mediaelement/pull/1123) [kernel] +* Fixed display of volume control on non-mobile touch devices (https://github.com/johndyer/mediaelement/pull/1093) [OwenEdwards] +* Calculate correctly the video player height for 100% (https://github.com/johndyer/mediaelement/pull/1083) [LeResKP] +* restore focus after click on the controls (https://github.com/johndyer/mediaelement/pull/1094) [rounce] +* Support youtu.be URL for youtube source (https://github.com/johndyer/mediaelement/pull/1135) [clkao] +* Make slider work on touch devices (https://github.com/johndyer/mediaelement/pull/1033) [Singularetantum] +* add Simplified Chinese translation (https://github.com/johndyer/mediaelement/pull/1065) [michaeljayt] +* Fixed the reference to `media` in the bigPlay control creation. (https://github.com/johndyer/mediaelement/pull/1111) [nuzzio] +* Fix layout bug when zooming page (https://github.com/johndyer/mediaelement/pull/1097) [ChiChou] +* Fix fullscreen iframe zoom bug. (https://github.com/johndyer/mediaelement/pull/1070) [lisbakke] + +*2.13.2 (2014/01/24)* + +* Removed breaking `hasTouch` detection +* Fixed IE detection https://github.com/johndyer/mediaelement/pull/1018 +* fix play() on ipad does not start playing and double click issue (https://github.com/johndyer/mediaelement/pull/918) [fbuecklers] +* added scale=default to Flash for better 100% https://github.com/johndyer/mediaelement/pull/963 +* Add code fences for GHFM https://github.com/johndyer/mediaelement/pull/975 +* i18n improvements https://github.com/johndyer/mediaelement/pull/1025 + +*2.13.1 (2013/09/?06)* + +* Support for fullscreen in IE11 beta + +*2.13.0 (2013/09/01)* + +* BREAKING FLASH SECURITY CHANGE: Removed `allowDomain("*")` by default. If you use MediaElement.js on a different domain use the `flashmediaelement-cdn.swf` file (nacin) https://github.com/johndyer/mediaelement/pull/956 +* Use only FlashVars and ignore parameters passed via query string. +* Force LTR in controls (for RTL users) (nacin) https://github.com/johndyer/mediaelement/pull/958 + +*2.12.1 (2013/08/26)* + +* Remove all `console.log` statements in `Builder.py` JD +* More i18n fixes for Wordpress (SergeyBiryukov) https://github.com/johndyer/mediaelement/pull/940 +* Fix touch detection in QtWebKit (peterbrook) https://github.com/johndyer/mediaelement/pull/939 +* Added configuration option httpsBasicAuthSite fix sites using HTTPS basic authentication (benroy73) https://github.com/johndyer/mediaelement/pull/937 +* Fixed backlight plugin error (eviweb) https://github.com/johndyer/mediaelement/pull/932 +* Fix some wrong dates on the change log (heartcode) https://github.com/johndyer/mediaelement/pull/930 +* Add a mejs-fullscreen css class on the root element (fbuecklers) https://github.com/johndyer/mediaelement/pull/925 +* fix for ff switch between fullscreen and normal mode (fbuecklers) https://github.com/johndyer/mediaelement/pull/924 +* Multiple fixes: old issue #548, current issues #754 and #902 (peterh-capella) https://github.com/johndyer/mediaelement/pull/923 +* fix firefox detect 100% mode issue (KaptinLin ) https://github.com/johndyer/mediaelement/pull/919 +* Option to show the poster when the video is ended (LeResKP) https://github.com/johndyer/mediaelement/pull/891 +* Fix for Chrome autoplaying when forcing Flash (tjsnyder) https://github.com/johndyer/mediaelement/pull/889 +* Allow SWF to work over insecure domain (sebablanco ) https://github.com/johndyer/mediaelement/pull/897 +* Corrected buffering height on CSS (SourceR85 ) https://github.com/johndyer/mediaelement/pull/875 +* CSS cleanup (awittdesigns) https://github.com/johndyer/mediaelement/pull/883 + + +*2.12.0 (2013/06/02)* + +* Removed old media files from repo (reduced filesize from 150MB to 25MB) +* Added `test.html` to `/tests/` folder to use JS files in `/src/` folder +* Fullscreen plugin player toggles play/pause when controls are clicked (JeffreyATW) https://github.com/johndyer/mediaelement/pull/742 +* Making use of pluginWidth & pluginHeight (simonschuh) https://github.com/johndyer/mediaelement/pull/837 +* Proportional poster images (IE9+ Chrome, Safari, Firefox) (eyefood) https://github.com/johndyer/mediaelement/pull/838 +* Fixed video resolution on seek in flash (efEris) https://github.com/johndyer/mediaelement/pull/839 +* Option for custom error message when no plugins are found. (svoynow-lz) https://github.com/johndyer/mediaelement/pull/842 +* Fix for Safari to play video on HTTPS site (benroy73) https://github.com/johndyer/mediaelement/pull/845 +* Fixes Mute/UnMute when playing from a YouTube source (mbaker3) https://github.com/johndyer/mediaelement/pull/848 +* i18n fixes for better compatibility with WordPress (SergeyBiryukov) https://github.com/johndyer/mediaelement/pull/850 +* Fixing invalid characters restrictions for URLs (sebablanco) https://github.com/johndyer/mediaelement/pull/859 +* Checking for pluginType on media instead of mediaelementplayer in Fullscreen (JeffreyATW) https://github.com/johndyer/mediaelement/pull/865 +* Problem with IE9 on Windows 7 N / Windows 7 KN without WMP installed (sarvaje) https://github.com/johndyer/mediaelement/pull/868 +* Cleanup stylesheet (jawittdesigns) https://github.com/johndyer/mediaelement/pull/867 +* Properly treat namespace-only events for `globalUnbind()` (odnamrataizem) https://github.com/johndyer/mediaelement/pull/878 +* Fixed issue with slash character separating time (S2) https://github.com/johndyer/mediaelement/pull/879 + +*2.11.3 (2013/04/13)* + +* Change to `getScriptPath` to allow querystring variables to be added (for Wordpress Core) + +*2.11.2 (2013/04/12)* + +* Fixed overly aggressive XSS testing (excluding forward slashes) +* Fixed line endings on Flash (*.as) files (markjaquith) (https://github.com/johndyer/mediaelement/pull/834) +* Included protocol relative URL for YouTube (Dan Tsosie) (https://github.com/johndyer/mediaelement/pull/832) + +*2.11.1 (2013/04/11)* + +Major changes + +* Removed Ogg, WebM, and MP3 files to keep download under 10MB. Files are now at https://github.com/johndyer/mediaelement-files +* Simple Flash Pseudo-streaming [set enablePseudoStreaming:true, pseudoStreamingStartQueryParam:'start'] (BryanMorgan) (https://github.com/johndyer/mediaelement/pull/814) +* Fixed possible XSS attack through `file=` parameter in `flashmediaelement.swf` + +Fixes and updates + +* Protocol relative YouTube URLs for `iframe` API (dtsosie) (https://github.com/johndyer/mediaelement/pull/825) +* Added aria-label to all button elements (Luzifer) (https://github.com/johndyer/mediaelement/pull/824) +* Fixed preroll adclick URL (johndyer) +* Traditional chinese locale strings for i18n module (latzt) (https://github.com/johndyer/mediaelement/pull/820) +* Allow captions on audio player (LeResKP) (https://github.com/johndyer/mediaelement/pull/819) +* Fix incorrect path returned by `getScriptPath()` (Ciki) (Fix incorrect path returned by getScriptPath()) +* Overhauling hover div creation and placement (JeffreyATW) (https://github.com/johndyer/mediaelement/pull/813) +* Clear timeout for second fullscreen stretch attempt (JeffreyATW) (https://github.com/johndyer/mediaelement/pull/812) +* fix type resolution when extension is uppercased (jbdemonte) (https://github.com/johndyer/mediaelement/pull/801) +* "splice is not a function" fix on `MediaElementPlayer.remove()` (odnamrataizem) (https://github.com/johndyer/mediaelement/pull/799) +* Make Flash stage handle CLICK rather than MOUSE_DOWN (odnamrataizem) (https://github.com/johndyer/mediaelement/pull/804) + + +*2.11.0 (2013/03/13)* + +* Preroll ads manager +* VAST ads plugin (sponsored by Minito Video) +* Slides `<track>` type (non-standard HTML5 use) +* Calculate rails size only with visible elements (romanbsd) (https://github.com/johndyer/mediaelement/pull/773) +* Round calculations of progress bar to prevent fractions (romanbsd) (https://github.com/johndyer/mediaelement/pull/768) +* Fix AndroidUseNativeControls (LeResKP) (https://github.com/johndyer/mediaelement/pull/749) +* Muting the volume icon if startVolume is set to 0 (heartcode) (https://github.com/johndyer/mediaelement/pull/747) +* Make YouTube URL protocol relative (strworkstation) (https://github.com/johndyer/mediaelement/pull/761) +* Prevent Flash audio player from sending too many 'progress' events (johndyer) +* Properly clean up player when calling MediaElementPlayer.remove() (odnamrataizem) (https://github.com/johndyer/mediaelement/pull/779) +* Add "mejs-shim" class to all shims to prevent improper resizing (JeffreyATW) (https://github.com/johndyer/mediaelement/pull/789) +* Bug fix for the error "this.pluginApi.pauseMedia is not a function" when using the flash player and removing the dom element. (Jmaharman) https://github.com/johndyer/mediaelement/pull/788 +* Make possible to open youtube links as audio only (Seb33300) https://github.com/johndyer/mediaelement/pull/784 +* Add a few basic Jasmine tests (msgilligan) https://github.com/johndyer/mediaelement/pull/781 +* Add option to hide the video controls on load (eResKP) https://github.com/johndyer/mediaelement/pull/780#issuecomment-14781622 +* [cc] button can now be a toggle when there's just one track (LeResKP) https://github.com/johndyer/mediaelement/pull/793 +* fixed error when srclang was missing + +*2.10.3 (2013/01/27)* + +* Fix broken scrollbar from API reference error (peterbrook) (https://github.com/johndyer/mediaelement/pull/739) + +*2.10.2 (2013/01/26)* + +* The project is now MIT-only, instead of dual licensed MIT and GPL (just as jQuery has done: http://jquery.org/license/) +* Fix audio height in 100% mode (https://github.com/johndyer/mediaelement/pull/667) +* Make rewinding at the end optional (https://github.com/johndyer/mediaelement/pull/725) +* Bugfix: attributes for PluginMediaElement (https://github.com/johndyer/mediaelement/pull/722) +* Add mejs-long-video class when capture is 1hr or longer, custom styles (https://github.com/johndyer/mediaelement/pull/715) +* Fix for dragging playhead horizontally off the video (https://github.com/johndyer/mediaelement/pull/711) +* Align timing of captions with show/hide controls (https://github.com/johndyer/mediaelement/pull/708) +* Missing semicolon (https://github.com/johndyer/mediaelement/pull/737) +* Don't send timeupdate event after ended event (https://github.com/johndyer/mediaelement/pull/727) +* Added option to disable pause/play on main div click (https://github.com/johndyer/mediaelement/pull/735) + +*2.10.1 (2012/12/31)* + +* New postroll feature (https://github.com/johndyer/mediaelement/pull/660) +* PluginMediaElement click-to-pause behavior doesn't work (https://github.com/johndyer/mediaelement/pull/691) +* Use the normal CSS property name after the vendor prefix (https://github.com/johndyer/mediaelement/pull/686) +* Select first source that is supported by the browser (https://github.com/johndyer/mediaelement/pull/679) +* fixed outerWidth for jQuery 1.8 compatiability (https://github.com/johndyer/mediaelement/pull/680) +* Fix for Issue #676 when Stop button does not behaves as expected in selected browsers (https://github.com/johndyer/mediaelement/pull/678) +* Fix source switching on Webkit in SourceChooser (https://github.com/johndyer/mediaelement/pull/675) +* Better 100% mode handling within non-visible container (https://github.com/johndyer/mediaelement/pull/668) +* Display chapter tracks for late-loading video sources, including YouTube (https://github.com/johndyer/mediaelement/pull/665) +* Added SVG Stop icon (https://github.com/johndyer/mediaelement/pull/696) +* Added SVG source chooser icon (https://github.com/johndyer/mediaelement/pull/669) +* Adding rounding to volume slider left, top, and and width setters (https://github.com/johndyer/mediaelement/pull/684) +* Display chapter tracks for late-loading video sources, including YouTube (https://github.com/johndyer/mediaelement/pull/665) + +*2.10.0 (2012/11/23)* + +* Support of matchMedia where possible [zachleat] +* Fix for 100% audio using correct sizing [dougwilson] +* SVG icons for better Retina support [johndyer] +* Localized buttons [latzt] https://github.com/johndyer/mediaelement/pull/627 +* Volume handle doesn't set initial position properly [JeffreyATW] https://github.com/johndyer/mediaelement/pull/625 +* Cleaned up some CSS whitespace https://github.com/johndyer/mediaelement/pull/656 +* Vimeo - updated to iframe code (from old megaloop) + +*2.9.5 (2012/09/26)* + +* Fixed faulty FlashMediaElement.swf (due to Git program mashing it) +* Fixed track element issues introduced by DFXP captions + +*2.9.4 (2012/09/24)* + +* Improved RTMP parsing [pansapien] https://github.com/johndyer/mediaelement/pull/574 +* Added `flashStreamer` option to separate streamer from file +* Raise an error for unknown video size in Flash [denmarkin] https://github.com/johndyer/mediaelement/pull/571 +* Fix for alwaysShowControls with keyboard interaction [peterh-capella] https://github.com/johndyer/mediaelement/pull/569 +* Support for DFXP captions [justinl-capella] https://github.com/johndyer/mediaelement/pull/420 + +*2.9.3 (2012/08/23) * + +* Allows use of `style="max-width: 100%;"` for responsive video +* Added type to source buttons in mep-feature-sourcechooser.js:48 [flamadiddle ] +* Fix use of inArray and $ in src/js/me-shim.js [lftl, Seb33300, eusonic and others] (this was a regression bug from another fix) +* Fixing syntax error in events demo [JeffreyATW] + +*2.9.2 (2012/07/06) * + +* Added a few height checks (from Joe Anderson) +* Removed console.log statements +* Better file MIME type detection when the "type" attribute is not set (Seb33300) +* Pass the event keyCode to the keyActions handler, and make seek interval configurable (bborn) +* Responsive flash fix, YouTube edits (heikki) +* New `auto_plugin` mode that starts with plugins then tries HTML5 (savil) + +*2.9.1 (2012/06/01)* + +* Fixed Firefox 10+ Fullscreen error + +*2.9.0 (2012/05/31)* + +* Fixed pointer-events detection in IE9 (when using Flash mode or YouTube) +* YouTube now shows annotations (using YouTube player rather than chromeless) +* Fix play/pause when clicking on video when overlays are displayed [markomarkovic] +* Dont listen to mouse events when there's not a reason to [neverarriving] +* Adding CSS animated buffer to the time rail [neverarriving] +* Fix for box-sizing: border-box from cutting off time text. [MatthewCallis] + +*2.8.2 (2012/05/15)* + +* Fixed volume slider bug when initially hidden +* Fixed YouTube size problems in Flash mode + +*2.8.1 (2012/04/19)* + +* Flash fullscreen: video not fullsized +* Flash fullscreen: youtube controls not working + +*2.8.0 (2012/04/17)* + +* Revamped YouTube to work using the Flash shim so that it supports fullscreen +* Fix for `remove()` method (lennym) +* Fix possible issue with ContextMenu ( quangvhg) +* Fix for stop button ( slavva97) +* Type on `var` and `;` (lennym) +* Fix for keyboard support forward and backward (myffical) + +*2.7.0 (2012/03/12)* + +* Added horizontal volume control, the new default for audio (based on work by [gavinlynch](http://github.com/gavinlynch)) +* Possible issues with < IE8 centering resolved +* Full set of controls under Silverlight ([Birol2010](https://github.com/Birol2010/)) +* YouTube fix [raknam] +* shim now has a .tagName property, and other DOM-like methods [tantalic] +* Poster display fix when HTML5, Flash, and Silverlight are all missing [bruha] +* Source Chooser plugin [markomarkovic] +* Fix for flash audio mute [lbernau] + +*2.6.5 (2012/02/01)* + +* Removed iOS 3.x poster code [xtat] [James Cross] +* Fixed bug when player is initially hidden in `display:none;` +* Workaround for when inside an `<iframe>` and Chrome doesn't correctly report exiting from fullscreen + +*2.6.4 (2012/01/10)* + +* Fixed a Flash bug when one video ended and another was loaded through `setSrc()` and `load()` +* Option for markup between current time and duration [tantalic] + +*2.6.3 (2012/01/08)* + +* Sending all options to Flash including colors + +*2.6.2 (2012/01/06)* + +* Fixed Flash fullscreen button inside an `<iframe>` +* Fixed flash auto starting in 100% mode + +*2.6.1 (2012/01/03)* + +* Updated Opera's Flash Fullscreen support (apparently, it doesn't like pointer-events:none with Flash) +* Added a `fullscreenchange` event to Flash to better track events + +*2.6.0 (2011/12/27)* + +* added major updates to Flash fullscreen mode controls [rmhall] +* added sneaky `pointer-events: none` to allow Flash to enter fullscreen in one clean click +* added missing CSS3 gradients syntaxes (kristerkari)[https://github.com/johndyer/mediaelement/pull/339] +* added check for left offset to detect when mousedrag exceeds top boundary [jmcneese](https://github.com/johndyer/mediaelement/pull/335) + +*2.5.0 (2011/12/15) - 56kb* + +* Flash fullscreen now works on hover, so it's much easier to use. For Firefox it's always on, but for others `usePluginFullScreen:true` option +* For the audio player, Flash objects are positioned outside the main `<div>` which allows the player to be hidden without breaking flash +* Volume controls was adjusted slightly +* Removed Google translate features (Google killed the API) + +*2.4.3 (2011/12/10)* + +* keyboard controls are now an array, allowing multiple keys to do the same thing +* support for Google TV keybuttons (based on above) +* arrow keys now move when paused +* floating time is now handled via JavaScript instead of CSS :hover (and removed from touch devices) + +*2.4.2 (2011/12/06) - 57.3kb* + +* keyboard controls (up/down controls volume, left/right seeks, space play/pause, f goes fullscreen) +* `<audio>` now works with 100% for responsive layouts [283](https://github.com/johndyer/mediaelement/issues/283) +* Support for auto start with class `mejs-player` and `data-mejsoptions` e.g. `<video src="media.mp4" class="mejs-player" data-mejsoptions='{"features":["playpause","progress","volume"}, "success": "myCallback"}'><video>` +* With multiple players on a page, when one starts the others pause (toggle `pauseOtherPlayers: true`) [285](https://github.com/johndyer/mediaelement/issues/285) + +*2.4.1 (2011/12/05) - 55.7kb* + +* Fixed fullscreen bug with Firefox (with Video for Everybody syntax) [270](https://github.com/johndyer/mediaelement/issues/270) +* Added `remove()` method to `MediaElement` and `MediaElementPlayer` to safely remove Flash (from IE) [111](https://github.com/johndyer/mediaelement/issues/111) +* Added a demo of MEJS skins to the /demo/ folder +* Closed issue with `ended` event in Flash (my example works) [246](https://github.com/johndyer/mediaelement/issues/246) +* Flash has better support for `preload="auto"` [290](https://github.com/johndyer/mediaelement/issues/290) + +*2.4.0 (2011/11/28) - 54.9kb* + +* Integration with YouTube API (and intial support for Vimeo API) : http://mediaelementjs.com/examples/?name=youtube +* Catch when Google Translate fails due to API limits + +*2.3.3 (2011/11/21) - 49.4kb* + +* removed volume controls for touch devices (Android and iOS require hardware volume) +* set a timeout to hide controls on touch devices +* fixed timecode bug with :09 (used radix) +* fixed bug when long videos end: (try/catch) +* fixed issue with `alwaysShowControls` +* removed a `console.log` in fullscreen that broke IE + +*2.3.2 (2011/11/12) 49.6kb* + +* removed `http` from Flash and Silverlight embeds to support SSL +* fixed a possible bug when neither `src` nor `type` was specified +* turned off useCapture for a few events + +*2.3.1 (2011/11/07)* + +* Another set of changes to handle various browser native fullscreen issues +* New control behavior for touch enabled devices (iPad, Android tablets) +* Bug fix for Flash (bradleyboy) + +*2.3.0 (2011/11/01) - 48.5kb* + +* Fixed bug when fullscreen was called before play pressed +* Additional classes mejs-audio, mejs-video, mejs-ios, mejs-iphone, mejs-ipad, mejs-android added to contianing `<div>` for styles +* IE9 can't use `<video width="100%">` so you must use either options ({videoHeight:'100%'}) or inline style `<video style="width:100%;height:100%;">` +* updated fullscreen code for Safari (erktime) +* loading indicators shows during 'waiting' event +* iOS and Android now show "big play" button again (sometimes overlaps on iPhone) + +*2.2.5 (2011/10/14)* + +* fix for Flash fallback in certain scenarios (IE RegExp problem, Firefox fullscreen Flash issue) +* adjustments for floating time indicator + +*2.2.4 (2011/10/10)* + +* True FullScreen support in Firefox (nightly) and Chrome (Canary) +* more updates for 100% mode +* enableContextMenu(), disableContextMenu() methods +* change to poster code to let it be set later + +*2.2.3 (2011/10/07b) - 45.8kb* + +* updated accessibility again for JAWS and NVDA (thanks to twitter.com/mohammed0204) +* added CSS class `<html class="mejs-embed">` for `<iframe>` embeds + +*2.2.2 (2011/10/07) - 45.8kb* + +* added support for <del>`<video width="100%" height="100%"></video>`</del> `<video style="width:100%;height:100%"></video>` (i.e. responsive/adaptive players) +* added :focus state for buttons to improve accessibility +* added title and aria-controls attributes to buttons to improve accessibility +* changed when loading circle appears (WebKit fires the 'loadstart' event differently than FF or IE) + +*2.2.1 (2011/10/06) - 44.1kb* + +* fixed a bug with fullscreen that caused IE to completely mess up it layout +* fixed another bug with fullscreen and z-index + +*2.2.0 (2011/10/04)* + +* controls now display on iPad, iPhone, and Android. Can be turned off using (iPadForceNativeControls:true) +* fullscreen support for iPad (different from true fullscreen on Safari 5.1) +* added frameaccurate timecode (via gselva) +* added contextmenu as a feature. if turned on the default includes: fullscreen toggle, mute toggle, and media download +* updated WebVTT support (still had some SRT formatting restrictions) +* dynamic player creation: from `<a href="media.mp4">video</a>` and `<div class="mejs"></div>` specifying type (string or array) +* Fixed bug where Flash couldn't go fullscreen with track chapters +* fixed a bug with Flash fullscreen ratios +* controls now disappear on timeout when mouse is idle (useful for fullscreen) +* enableControls() and disableControls() (for pre/post roll scenarios) +* added an autoplay override (especially for WebKit browsers) +* fixed functionality of mute toggling +* reorganized plugins to use $.extend +* updating functionality of loading graphic to account for various browser inconsistencies (loadstart event) + +*2.1.9 (2011/08/04) - 36.9kb* + +* fixed Android 2.1 and 2.2 playing problems (still need a good 2.3 and 3.0 device. hint. hint.) + +*2.1.8 (2011/08/03) - 36.9kb* + +* True fullscreen for Safari 5.1 +* Flash/Silverlight fullscreen is now "full window" (except for Firefox which cannot handle adjusting Flash without reloading it) + +*2.1.7 (2011/07/19) - 35.9kb* + +* fixed mute button (kaichen) +* added alwaysShowControls option (kaichen) +* forceful padding override on buttons +* started "ender" branch to experiment with removing jQuery dependency and baking in ender.js +* updated the use of `type` javascript option with src is present +* remove preload="none" hack for Chrome now that it supports it (note: Chrome still strangely fires a 'loadstart' event) +* added hooks for other jQuery compatible libraries like [ender.js](http://enderjs.com) +* Wordpress: if you don't specify a file extension, mejs will look for attached files and use them [video src="/wp-content/uploads/myfile"] +* Wordpress: option to select a 'skin' +* Wordpress: option to select audio width/height + +*2.1.6 (2011/06/14) - 35.5kb* + +* fix errors when the progress bar isn't present +* buttons are now actual `<button>` tags which allows tabbed controls (for better accessibility and possible ARIA support) +* fix problems with low volume in Flash on startup (startVolume was sometimes 0!) +* updated a few places to use jQuery 1.6's new prop/attr methods +* updated skins to account for new `<button>` (still need highlighted style) + +*2.1.5 (2011/05/28) - 35.2kb* + +* minor fix for controls not showing time or duration +* when switching files, the Flash plugin now forcibly stops downliading + +*2.1.4 (2011/05/20) - 35.2kb* + +* fixed display of hours +* fixed Flash audio bug where pausing when the file wasn't fully loaded would cause the progress bar to go offscreen +* fixed Flash video bug where percent loaded was always 100% +* fixed Flash audio bug where pressing pause, then play would always restart playback from the beginning +* startVolume works more clearly in plugins (esp. Opera and Linux) +* tracks support no longer refers to WebSRT, but is more generic for WebVTT (not all features of WebVTT are supported yet) +* fixed fullscreen in Safari OS X 10.5 (which doens't really support true fullscreen) +* Flash and Silverlight can now start downloading if preload="auto" or preload="metadata" (warning: preload="metadata" will load the entire thing) + +*2.1.3 (2011/04/12) - 35.8kb* + +* added support for hours in time format (00:00:00) and an alwaysShowHours option to force hours to always show +* removed some duplicate flash events +* added 'seeking' event to Flash/SL (already had 'seeked') + +*2.1.2 (2011/03/23) - 34.4kb* + +* fixed IE6 and IE7 caption position +* fixed IE7 failure in certain places +* changed browser UA detection to use only lowercase (iPhone iphone) +* fixed Flash audio loaded bug (reporting 0 after loaded) +* added removeEventListener to shims +* new rail-resizing code + +*2.1.1 (2011/03/07) - 33.5kb* + +* added 'loadeddata' event to Flash and Silverlight +* switched to flashvars parameter to support Apache's mod_security +* better flash fullscreen support +* added flv-x to flash's accepted types +* Fixed a bug in poster sizing (only affected IE) +* added "isFullScreen" property to media objects (like Safari's webkitDisplayingFullscreen) +* controls start hidden with autoplay +* fixed iOS loading issues (success wasn't firing, other errors) +* fixed IE6 when using new MediaElementPlayer(), rather than jQuery + +*2.1.0 (2011/02/23) - 32.9kb* + +* Updated control styles for a cleaner look +* Added loadeddata and canplay events to Flash and Silverlight +* Added loading indicator to MediaElementPlayer +* Added stop button (pause, then return to currentTime:0) +* IE6/7 CSS updates +* Poster is now forced to the size of the player (could be updated to be proportional if someone wants to add that) +* Updated Flash ended event to account for buffering weirdness +* Fixed a track text hovering problem + +*2.0.7 (2011/02/13) - 31.9kb* + +* Added 'mode' option to force native (HTML5) or shim (Flash,Silverlight) modes +* Fixed audio seeking bug in Flash (thanks Andy!) +* Fixed startVolume not working in Flash +* Overrided Chrome's autoplay since it doesn't always work + +*2.0.6 (2011/02/04) - 31.7kb* + +* Whitespace cleanup on files +* Preventing flash/sl plugins from reinitializing when they are removed by another script +* Fixed IE JavaScript errors in Flash fallback (seen in Wordpress) +* Added 'play' event to Silverlight to prevent errors + +*2.0.5 (2011/01/25) - 31.7kb* + +* Added error object to player +* Adjusted popup timer and progress bar +* Fixed media URL escaping +* Stopped sending poster to plugin +* Silverlight culture update +* Added back reference check (also makes jQuery usage easier) +* Added stop() function to mediaelement +* timerupdate still fires when paused (plugins) +* Added Security.allowDomain("*") to Flash so it can be used on different domains +* Fixed progress bar for Firefox 3 with Ogg files +* Prevented Flash from re-creating the player when show/hide restarts it +* Fixed initial volume level in non-HTML5 players +* Made PNG8 versions of controls images (for IE6) + +*2.0.4 (2011/01/14) - 31.2kb* + +* Fixed a major bug in plugin detection. + +*2.0.3 (2011/01/13) - 31.2kb* + +* changed IE Flash insertion to include me-plugin CSS class +* changed player error handling +* fixed a bug in the Silverlight player related to URLs + +*2.0.2 (2010/12/31) - 31.1kb* + +* Changed HTML escape method to encodeURICompnent +* Flash-based RMTP support (contributor: sylvinus) +* Fixed Wordpress loop bug +* Changed time popup to move with mouse instead of currentTime +* added enablePluginSmoothing (Flash) +* Added some "play" "playing" event duplication to Flash + +*2.0.1 (2010/12/20) - XX.Xkb* + +* Changed Flash to allow cross domain video +* Added 'click' event to Flash and Silverlight +* Updated autoplay attribute detection + +*2.0.0 (2010/12/13) - 30.8kb* + +* Reorganized MediaElementPlayer code to allow each button to become a pluggable feature that can be removed or overrided +* Enabled a no JavaScript version to support Video for Everybody nested syntax (optional) +* Enabled drag on progress bar +* Preload="none" is default for Flash and Silverlight +* Preload="none" enabled on Google Chrome +* Added skins to download +* Support for skin swapping +* Updated volume handle controls +* Update progress controls display +* Exposed MediaElement API methods on player +* Adjusted layout for IE6 + +*1.1.7 (2010/11/29) - 29.8kb* + +* Fixed bug with `<track>` loading on `<audio>` player + +*1.1.6 (2010/11/23) - 29.8kb* + +* Chapters support `<track kind="chapters" />` + +*1.1.5 (2010/11/21) - 29.8kb* + +* Workaround for IE issues when accidentally placed inside `<p>` tag +* Fixed silverlight pause state reporting +* Switched back to Flash as default +* Removed requirement for Google translate API `<script>` (direct JSONP call) +* Added googleApiKey option + +*1.1.4 (2010/11/21) - 29.5kb* + +* Added Default volume level to options (0.8) +* Fix for IE volume slider positioning +* Fix for IE tracks parsing (replacement String.split) +* Changed namespace from html5 to mejs +* Remove all showMessage references +* Controls show again after playback ends + +*1.1.3 (2010/11/20) - 29.0kb* + +* Change to fallback mechanism and styling (Windows Phone 7) + +*1.1.2 (2010/11/19) - 28.9kb* + +* Removed messages, added big play button +* Google translate now supports more than 1000 characters +* Added a dropdownlist of languages from which the user can select +* Added timerUpdate option to set the millisecond speed of timeupdate events +* Updated the media file and examples + +*1.1.1 (2010/11/18) - 27.1kb* + +* added captioning support via the `<track>` tag (thanks to [Playr](http://www.delphiki.com/html5/playr) for the example) +* added auto-translation support via Google translate API + +*1.1.0 (2010/11/17) - 22.6kb* + +* Total re-oganization of MediaElement, MediaElementPlayer, and supporting objects +* Updated CSS to a cleaner look, with better IE support & big play button +* Simplified all plugin and version detection +* Added loop option (useful for audio files) +* Added the ability to turn each control button on/off +* Added canPlayType to PluginMediaElement +* Updated setSrc to take multiple sources + +*1.0.7 (2010/11/16) - 18.15kb* + +* Total re-oganization of MediaElement code +* JSLint compliant, YUI compliant + +*1.0.6 (2010/11/15) - 17.96kb* + +* Rebuilt PluginDetection (removed SWFObject and Microsoft code) +* More JSLint compatible (still a few iterations to get there) +* Added jQuery 1.4.4 + +*1.0.5 (2010/11/10 later on)* + +* Fixed a problem with the *.min.js files +* Added jQuery 1.4.3 + +*1.0.4 (2010/11/10) - 18.32kb* + +* Fixed Flash display when `<video>` did not match actual dimensions +* autosizing in Flash and Silverlight +* added options for defaultVideoWidth, defaultVideoHeight when `<video>` `height` and `width` are not set +* included minified versions using YUI compressor + +*1.0.3 (2010/09/24)* + +* changes in poster handling +* fix IE9 startup bug (its 'play' event fires wrongly it seems) +* fixed Flock, Opera sizing bugs +* fixed audio ended bug in special cases under Flash +* added default height/width when they are not specified in attributes + +*1.0.2 (2010/09/17)* + +* minor updates to support IE9 beta1 + +*1.0.1 (2010/09/13)* + +* added native fullscreen support for Safari 5 (via webkitEnterFullScreen) + +*1.0.0 (2010/08/09)* + +* initial release + +###TODO + +2.x features +* HTML5 error handling +* Flash/SL error codes +* Postroll +* Flash StageVideo? + + + +*Someday/Maybe features* + +* deeper WebVTT support (alignment, color, etc.) - include captionator +* Full support for Ender.js, including mediaelement-and-player-standalone which includes ender. +* thin line when controls are off +* system-wide events +* Ogg/Theora playback +* Better alignment with native MediaElement (using shimichanga.com techniques) + diff --git a/js/mediaelement/demo/hls_streams.js b/js/mediaelement/demo/hls_streams.js new file mode 100644 index 0000000000000000000000000000000000000000..dfbe04cad0f9a9867bdaf1f383592ae9cdcd38ed --- /dev/null +++ b/js/mediaelement/demo/hls_streams.js @@ -0,0 +1,74 @@ +var teststreams = [{ + file:'http://streambox.fr/playlists/test_001/stream.m3u8', + title: 'VOD (6 levels) - ffmpeg internal segmenter x264+aac' +},{ + file:'http://edge-1.2gzr.com/5178d5fe531a484b777dacf1.m3u8', + title: 'simple HLS stream' +},{ + file:'http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8', + title: 'BigBuckBunny 2 Levels' +},{ + file:'http://m4stv.inqb8r.tv/studentTV/studentTV.stream_360p/playlist.m3u8', + title: 'Live' +},{ + file:'http://www.codecomposer.net/hls/playlist.m3u8', + title: 'Discontinuity' +},{ + file:'http://streambox.fr/playlists/issue_002/test.m3u8', + title: 'HLSprovider/issues/2' +},{ + file:'http://streambox.fr/playlists/issue_003/index.m3u8', + title: 'HLSprovider/issues/3 - envivio encoder, big delay between audio and video PTS' +},{ + file:'http://streambox.fr/playlists/issue_004_1/index.m3u8', + title: 'HLSprovider/issues/4 - envivio encoder, AAC frames cut between 2 fragments, PES parsing robustness' +},{ + file:'http://streambox.fr/playlists/issue_004_2/index.m3u8', + title: 'HLSprovider/issues/4 - envivio encoder, AAC frames cut between 2 fragments, TS parsing robustness' +},{ + file:'http://streambox.fr/playlists/issue_005/index.m3u8', + title: 'HLSprovider/issues/5' +},{ + file:'http://streambox.fr/playlists/issue_006/sample.m3u8', + title: 'HLSprovider/issues/6 - TS parsing robustness' +},{ + file:'http://streambox.fr/playlists/issue_010/list.m3u8', + title: 'HLSprovider/issues/10 - drift between segment and playlist duration' +},{ + file:'http://streambox.fr/playlists/issue_012/playlist.m3u8', + title: 'HLSprovider/issues/12 - PTS/seqnum not synchronized accross adaptive playlists' +},{ + file:'http://streambox.fr/playlists/issue_020/new/test.m3u8', + title: 'HLSprovider/issues/20 - AVC NAL unit parsing issue' +},{ + file:'http://streambox.fr/playlists/issue_026/stream_multi.m3u8', + title: 'HLSprovider/issues/26 - bad segmentation - adaptive' +},{ + file:'http://streambox.fr/playlists/issue_026/stream_cell_16x9_440k.m3u8', + title: 'HLSprovider/issues/26 - bad segmentation - 440k' +},{ + file:'http://inx-con001.inqb8r.tv/live/e4/playlist.m3u8', + title: 'HLSprovider/issues/43 - discontinuity on live streams - multiple bitrate' + },{ + file:'http://avideos.5min.com/28/5180028/518002744_20131106_212846.m3u8', + title: 'HLSprovider/issues/48 - multiple bitrate - first level = AAC elementary stream' + },{ + file:'http://streambox.fr/playlists/issue_066/prog_index.m3u8', + title: 'HLSprovider/issues/66 - accurate seeking - multiple keyframes per segment - playback artifacts' + },{ + file:'http://streambox.fr/playlists/issue_067/stream.m3u8', + title: 'HLSprovider/issues/67 - VLC as HLS server - decoding issues' + },{ + file:'http://193.218.104.234:8080/hls/test_1200.m3u8', + title: 'No Audio - Live Playlist' +},{ + file:'http://live.gamt.su/20131128/archive-m3u8-aapl.ism/Manifest(format=m3u8-aapl).m3u8', + title: 'Mariinsky Theatre - HD (5 A/V levels from 640kb/s to 4.7 Mb/s + 1 audio fallback 260kb/s)' +},{ + file:'http://playertest.longtailvideo.com/adaptive/mandolin2/optimized.m3u8', + title: 'adaptive playlist - first level = AAC elementary streams with 2 ID3 tag' +},{ + file:'http://dev.movingbits.nl/playlist/testing/encrypted/index.m3u8?player_id=Testing123456789&public=true', + title: 'one level - AES 128 - one key, no IV. movingbits.nl' +} +]; diff --git a/js/mediaelement/demo/index.html b/js/mediaelement/demo/index.html new file mode 100644 index 0000000000000000000000000000000000000000..4516b2705a06c2258eff4a8aade42983936f7a55 --- /dev/null +++ b/js/mediaelement/demo/index.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>MediaElement.js</title> +</head> +<body> + +<h1>MediaElement.js</h1> + +<ul> + <li><a href="mediaelement.html">MediaElement</a> - core library, no player</li> + <li><a href="mediaelementplayer.html">MediaElementPlayer - Default</a> - default player install</li> + <li><a href="mediaelementplayer-audio.html">MediaElementPlayer - Audio</a> - audio player example</li> + <li><a href="mediaelementplayer-skins.html">MediaElementPlayer - Skins</a> - CSS skins demo</li> + <li><a href="mediaelementplayer-track.html">MediaElementPlayer - Track</a> - track and translation demo</li> + <li><a href="mediaelementplayer-sourcechooser.html">MediaElementPlayer - Source Chooser</a> - multiple sources</li> + <li><a href="mediaelementplayer-frameaccurate.html">MediaElementPlayer - Timecode and Framecount </a> - Timecode and Framecount demo</li> + <li><a href="mediaelementplayer-hls.html">MediaElementPlayer - HLS</a> - controls on HLS video</li> + <li><a href="mediaelementplayer-youtube.html">MediaElementPlayer - YouTube</a> - controls on YouTube video</li> + <li><a href="mediaelementplayer-responsive.html">MediaElementPlayer - Responsive</a> - 100% player with YouTube</li> + <li><a href="mediaelementplayer-events.html">MediaElementPlayer - Events</a> - example of attaching events</li> + <li><a href="mediaelementplayer-postroll.html">MediaElementPlayer - Postroll</a> - showing something when video ends</li> + +</ul> + +</body> +</html> diff --git a/js/mediaelement/demo/mediaelement-stream.html b/js/mediaelement/demo/mediaelement-stream.html new file mode 100644 index 0000000000000000000000000000000000000000..c0fdffa9ad3d31a74d7bfc1a4cf42de4859eb935 --- /dev/null +++ b/js/mediaelement/demo/mediaelement-stream.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + <script src="../build/mediaelement.js"></script> +</head> +<body> + +<h1>MediaElement.js</h1> + +<p>This is only the Flash/Silverlight Shim for older browers or browsers without the codec you need. It is not a full player.</p> + +<h2>RTMP stream</h2> +<video width="360" height="203" id="player1" src="rtmp://stream2.france24.yacast.net/france24_live/en/f24_liveen" autoplay="true" type="video/flv" controls="controls"></video> + +<input type="button" id="pp" value="toggle" /> +<span id="time"></span> + +<script> +MediaElement('player1', {success: function(me) { + + me.play(); + + me.addEventListener('timeupdate', function() { + document.getElementById('time').innerHTML = me.currentTime; + }, false); + + document.getElementById('pp')['onclick'] = function() { + if (me.paused) + me.play(); + else + me.pause(); + }; + +}}); +</script> + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelement.html b/js/mediaelement/demo/mediaelement.html new file mode 100644 index 0000000000000000000000000000000000000000..46902312cd2787736da3f08d037258ec52796cf5 --- /dev/null +++ b/js/mediaelement/demo/mediaelement.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement.js"></script> + <script src="testforfiles.js"></script> +</head> +<body> + +<h1>MediaElement.js</h1> + +<p>This is only the Flash/Silverlight Shim for older browsers or browsers without the codec you need. It is not a full player.</p> + +<h2>MP4 video (as src)</h2> +<video width="360" height="203" id="player1" src="../media/echo-hereweare.mp4" type="video/mp4" controls="controls"></video> + +<input type="button" id="pp" value="toggle" /> +<span id="time"></span> + + + +<script> +MediaElement('player1', {success: function(me) { + + me.play(); + + me.addEventListener('timeupdate', function() { + document.getElementById('time').innerHTML = me.currentTime; + }, false); + + document.getElementById('pp')['onclick'] = function() { + if (me.paused) + me.play(); + else + me.pause(); + }; + +}}); +</script> + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer-audio.html b/js/mediaelement/demo/mediaelementplayer-audio.html new file mode 100644 index 0000000000000000000000000000000000000000..718872e1257aa17aefe2a648597d1138c94805ef --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-audio.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<p>Audio player</p> + +<h2>MP3</h2> +<audio id="player2" src="../media/AirReview-Landmarks-02-ChasingCorporate.mp3" type="audio/mp3" controls="controls"> +</audio> + +<script> +$('audio,video').mediaelementplayer(); +</script> + +</body> +</html> + diff --git a/js/mediaelement/demo/mediaelementplayer-events.html b/js/mediaelement/demo/mediaelementplayer-events.html new file mode 100644 index 0000000000000000000000000000000000000000..d2b408fa9a366cc562ea0bf01a2aa87882310814 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-events.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement - Events</title> + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<h2>Events Demo</h2> + +<video width="640" height="360" id="player1"> + + <source src="../media/echo-hereweare.mp4" type="video/mp4" title="mp4"> + <source src="../media/echo-hereweare.webm" type="video/webm" title="webm"> + <source src="../media/echo-hereweare.ogv" type="video/ogg" title="ogg"> + <p>Your browser leaves much to be desired.</p> + +</video> + +<div id="output"> +</div> + + +<span id="player1-mode"></span> + +<script> +$('video').mediaelementplayer({ + success: function(media, node, player) { + var events = ['loadstart', 'play','pause', 'ended']; + + for (var i=0, il=events.length; i<il; i++) { + + var eventName = events[i]; + + media.addEventListener(events[i], function(e) { + $('#output').append( $('<div>' + e.type + '</div>') ); + }); + + } + } +}); +</script> + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer-frameaccurate.html b/js/mediaelement/demo/mediaelementplayer-frameaccurate.html new file mode 100644 index 0000000000000000000000000000000000000000..b0cb15ed3a1ead6a7d627033a0583153e015c283 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-frameaccurate.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<h2>Frame accurate Playback with Timecode - Multi-codec</h2> + +<code><pre> +<video width="640" height="360" id="player2" poster="../media/frameaccuracy_logo.jpg" controls="controls" preload="none"> + + <!-- WebM for Firefox 4 and Opera --> + <source type="video/webm" src="../media/perfect-timecoded.webm" /> + + <source type="video/mp4" src="../media/perfect-timecoded.mp4" /> + + <!-- OGG for Firefox 3 --> + <source type="video/ogg" src="../media/perfect-timecoded.ogv" /> + <!-- Fallback flash player for no-HTML5 browsers with JavaScript turned off --> + <object width="640" height="360" type="application/x-shockwave-flash" data="../build/flashmediaelement.swf"> + <param name="movie" value="../build/flashmediaelement.swf" /> + <param name="flashvars" value="controls=true&poster=../media/frameaccuracy_logo.jpg&file=../media/perfect-timecoded.mp4" /> + <!-- Image fall back for non-HTML5 browser with JavaScript turned off and no Flash player installed --> + <img src="../media/frameaccuracy_logo.jpg" width="640" height="360" alt="Here we are" + title="No video playback capabilities" /> + </object> +</video> +</pre></code> + +<video width="640" height="360" type="video/mp4" + id="player1" poster="../media/frameaccuracy_logo.jpg" + controls="controls" preload="none"> + + <!-- WebM for Firefox 4 and Opera --> + <source type="video/webm" src="../media/perfect-timecoded.webm" /> + + <!-- MP4 source must come first for iOS? --> + <source type="video/mp4" src="../media/perfect-timecoded.mp4" /> + + <!-- OGG for Firefox 3 --> + <source type="video/ogg" src="../media/perfect-timecoded.ogv" /> + <!-- Fallback flash player for no-HTML5 browsers with JavaScript turned off --> + <object width="640" height="360" type="application/x-shockwave-flash" data="../build/flashmediaelement.swf"> + <param name="movie" value="../build/flashmediaelement.swf" /> + <param name="flashvars" value="controls=true&file=../media/perfect-timecoded.mp4" /> + <!-- Image fall back for non-HTML5 browser with JavaScript turned off and no Flash player installed --> + <img src="../media/frameaccuracy_logo.jpg" width="640" height="360" alt="Here we are" + title="No video playback capabilities" /> + </object> + + + </video> + +<span id="player1-mode"></span> + +<p><b>Note</b>: if you are working with local files, you'll need to add your working directory +to the <a href="http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html">Flash Global Security Settings</a>.</p> + + +<script> + +$('audio,video').mediaelementplayer({ + // show framecount in timecode (##:00:00:00) + showTimecodeFrameCount: true, + // used when showTimecodeFrameCount is set to true + framesPerSecond: 25, + + // Hide controls when playing and mouse is not over the video + alwaysShowControls: true, + + success: function(player, node) { + $('#' + node.id + '-mode').html('mode: ' + player.pluginType); + } +}); + +</script> + +<input type="button" id="stopall" value="Stop all"> + +<script> +$('#stopall').click(function() { + $('video, audio').each(function() { + $(this)[0].player.pause(); + }); +}); +</script> + + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer-hls.html b/js/mediaelement/demo/mediaelementplayer-hls.html new file mode 100644 index 0000000000000000000000000000000000000000..877587fcd3f48244fac7b72febfb5a051f96d52e --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-hls.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement - HLS</title> + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<h2>HLS Wrapper</h2> + +<code><pre> +<video width="640" height="360" id="player1" preload="none"> + + <!-- Pseudo HTML5 --> + <source type="application/x-mpegURL" src="http://www.streambox.fr/playlists/test_001/stream.m3u8" /> + +</video> +</pre></code> + +<video width="640" height="360" id="player1"> + + <!-- Pseudo HTML5 --> + <source type="application/x-mpegURL" src="http://www.streambox.fr/playlists/test_001/stream.m3u8" /> + +</video> + +<span id="player1-mode"></span> + +<h3> Test Videos </h3> +<div> The following videos should stream correctly. Each time an +issue is reported with a sample playlist, it will be added in the list +and verified after bugfixing </div> +<ul id="streamlist"> +</ul> + +Check with your own Playlist ! <a href="http://kb2.adobe.com/cps/142/tn_14213.html">beware of Cross Domain Policy</a><br> +<input id="userInput" value="http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8" size="80"> <button onclick="userSubmit()">Load and Play</button><br> +<p> + +<script type="text/javascript" src="hls_streams.js"></script> +<script type="text/javascript"> + +function listStreams(list, container) { +for(var i=0; i<list.length; i++) { var entry = document.createElement("li"); +entry.innerHTML = "<a href='#' onclick='return loadStream(\""+list[i].file+"\")'>"+list[i].title+"</a>"; +document.getElementById(container).appendChild(entry); +} +} +listStreams(teststreams, "streamlist"); + +function userSubmit() { + loadStream(document.getElementById('userInput').value); +} +function loadStream(url) { +$('video')[0].player.setSrc(url); +$('video')[0].player.play(); +} + +</script> + + +<script> + +$('video').mediaelementplayer({ + success: function(media, node, player) { + $('#' + node.id + '-mode').html('mode: ' + media.pluginType); + } +}); + +</script> + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer-inflash-autoresize.html b/js/mediaelement/demo/mediaelementplayer-inflash-autoresize.html new file mode 100644 index 0000000000000000000000000000000000000000..af498b2e9a5f70d354cd3d4a21eccfd136576ba4 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-inflash-autoresize.html @@ -0,0 +1,57 @@ + +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.js"></script> + + <style> + .video-container { + position: absolute; + top: 200px; + right: 20px; + left: 20px; + bottom: 50px; + } + + .mejs-container { + max-width: 100%; + max-height: 100%; + } + + </style> +</head> +<body> + <h1>MediaElementPlayer.js</h1> + + <h2>Autoresize In Flash Demo</h2> + + <code><pre> + <div class="video-container"> + <video style="max-width:100%; max-height:100%;" src="../media/echo-hereweare.mp4" type="video/mp4" id="player1" + controls="controls" preload="none"></video> + </pre></code> + + <div class="video-container"> + <video style="max-width:100%; max-height:100%;" src="../media/echo-hereweare.mp4" type="video/mp4" id="player1" + controls="controls" preload="none"></video> + </div> + + <script> + $('video').mediaelementplayer({ + mode: 'shim', + enableAutosize: false, + plugins: ['flash'], + success: function(player, node) { + $('#' + node.id + '-mode').html('mode: ' + player.pluginType); + } + }); + </script> + + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer-postroll-content.html b/js/mediaelement/demo/mediaelementplayer-postroll-content.html new file mode 100644 index 0000000000000000000000000000000000000000..f0b6d3aa661d4d9ecb7b063176b967183953f35c --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-postroll-content.html @@ -0,0 +1,4 @@ +<div class="postroll"> + <h3>Postroll content</h3> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p> +</div> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer-postroll.html b/js/mediaelement/demo/mediaelementplayer-postroll.html new file mode 100644 index 0000000000000000000000000000000000000000..b95e66772a516df0113f16965edb9e957bddafa1 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-postroll.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<p>Recommended Setup</p> + + +<h2>MP4/WebM video</h2> +<video width="360" height="203" id="player2" controls="controls"> + <source src="../media/echo-hereweare.mp4" type="video/mp4" title="mp4"> + <source src="../media/echo-hereweare.webm" type="video/webm" title="webm"> + <source src="../media/echo-hereweare.ogv" type="video/ogg" title="ogg"> + <p>Your browser leaves much to be desired.</p> + + <link rel="postroll" href="./mediaelementplayer-postroll-content.html" /> +</video> + +<script> +$('audio,video').mediaelementplayer({ + features: ['playpause','progress','volume','postroll'] +}); +</script> +<style> +.postroll { + padding: 5%; + color: #fff; +} +</style> + +</body> +</html> diff --git a/js/mediaelement/demo/mediaelementplayer-responsive.html b/js/mediaelement/demo/mediaelementplayer-responsive.html new file mode 100644 index 0000000000000000000000000000000000000000..6403850b63ab008b22c82f7418d1a8bc7d9abb68 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-responsive.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement - Responsive</title> + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> + +<style> +.wrapper { + width: 50%; +} +</style> + +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<h2>Responsive Demo</h2> + +<code><pre> +<video width="640" height="360" style="width: 100%; height: 100%;" id="player1" preload="none"> + + <!-- Pseudo HTML5 --> + <source type="video/youtube" src="http://www.youtube.com/watch?v=nOEw9iiopwI" /> + +</video> +</pre></code> + +<div class="wrapper"> +<video width="640" height="360" style="width: 100%; height: 100%;" id="player1"> + + <!-- Pseudo HTML5 --> + <source type="video/youtube" src="http://www.youtube.com/watch?v=nOEw9iiopwI" /> + +</video> +</div> + +<span id="player1-mode"></span> + +<script> + +$('video').mediaelementplayer({ + success: function(media, node, player) { + $('#' + node.id + '-mode').html('mode: ' + media.pluginType); + } +}); + +</script> + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer-skins.html b/js/mediaelement/demo/mediaelementplayer-skins.html new file mode 100644 index 0000000000000000000000000000000000000000..5608a21324613497e3b441953e19acc8f95c325d --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-skins.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> + + <link rel="stylesheet" href="../build/mejs-skins.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<p>Skins Demo. Just add a class on the <video class="mejs-myskin" > and then follow the examples in the mejs-skins.css file</p> + +<h2>Default Skin</h2> + +<video width="640" height="360" src="../media/echo-hereweare.mp4" type="video/mp4" + id="player1" poster="../media/echo-hereweare.jpg" + controls="controls" preload="none"></video> + +<h2>TED SKin</h2> + +<video class="mejs-ted" width="640" height="360" src="../media/echo-hereweare.mp4" type="video/mp4" + id="player1" poster="../media/echo-hereweare.jpg" + controls="controls" preload="none"></video> + + +<h2>WMP SKin</h2> + +<video class="mejs-wmp" width="640" height="360" src="../media/echo-hereweare.mp4" type="video/mp4" + id="player1" poster="../media/echo-hereweare.jpg" + controls="controls" preload="none"></video> + + +<script> +$('audio,video').mediaelementplayer({ + success: function(player, node) { + $('#' + node.id + '-mode').html('mode: ' + player.pluginType); + } +}); + +</script> + + +</body> +</html> diff --git a/js/mediaelement/demo/mediaelementplayer-sourcechooser.html b/js/mediaelement/demo/mediaelementplayer-sourcechooser.html new file mode 100644 index 0000000000000000000000000000000000000000..5f262346a75951825428ac178b010e8bc54f82d9 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-sourcechooser.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<p>Recommended Setup</p> + + +<h2>MP4/WebM video</h2> +<video width="360" height="203" id="player2" controls="controls"> + <source src="../media/echo-hereweare.mp4" type="video/mp4" title="mp4"> + <source src="../media/echo-hereweare.webm" type="video/webm" title="webm"> + <source src="../media/echo-hereweare.ogv" type="video/ogg" title="ogg"> + <p>Your browser leaves much to be desired.</p> +</video> + +<script> +$('audio,video').mediaelementplayer({ + features: ['playpause','progress','volume','sourcechooser'] +}); +</script> + +</body> +</html> diff --git a/js/mediaelement/demo/mediaelementplayer-src.html b/js/mediaelement/demo/mediaelementplayer-src.html new file mode 100644 index 0000000000000000000000000000000000000000..9d5b4e63f94b1c89851887b92a7327041b2a4942 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-src.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + <script src="../build/jquery.js"></script> + <script src="../src/js/me-header.js"></script> + <script src="../src/js/me-namespace.js"></script> + <script src="../src/js/me-utility.js"></script> + <script src="../src/js/me-plugindetector.js"></script> + <script src="../src/js/me-featuredetection.js"></script> + <script src="../src/js/me-mediaelements.js"></script> + <script src="../src/js/me-shim.js"></script> + + <script src="../src/js/mep-header.js"></script> + <script src="../src/js/mep-library.js"></script> + <script src="../src/js/mep-player.js"></script> + <script src="../src/js/mep-feature-playpause.js"></script> + <script src="../src/js/mep-feature-stop.js"></script> + <script src="../src/js/mep-feature-progress.js"></script> + <script src="../src/js/mep-feature-time.js"></script> + <script src="../src/js/mep-feature-volume.js"></script> + <script src="../src/js/mep-feature-fullscreen.js"></script> + <script src="../src/js/mep-feature-tracks.js"></script> + <script src="testforfiles.js"></script> + + <link rel="stylesheet" href="../src/css/mediaelementplayer.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<!-- simple single file method --> +<video width="640" height="360" src="../media/echo-hereweare.mp4" type="video/mp4" + id="player1" poster="../media/echo-hereweare.jpg" + controls="controls" preload="none"></video> + +<span id="player1-mode"></span> + +<script> + +$('audio,video').mediaelementplayer({ + pluginPath: '../build/', + success: function(player, node) { + $('#' + node.id + '-mode').html('mode: ' + player.pluginType); + console.log(player, node); + } +}); + +</script> + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer-track.html b/js/mediaelement/demo/mediaelementplayer-track.html new file mode 100644 index 0000000000000000000000000000000000000000..7e0e6c2ac00f07abf46332254c479ccc797f5775 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-track.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + <script src="https://www.google.com/jsapi"></script> + <script type="text/javascript"> + google.load("language", "1"); + </script> + + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<p>Recommended Setup</p> + + +<h2>MP4/WebM video</h2> +<video width="360" height="203" id="player2" controls="controls"> + <source src="../media/echo-hereweare.mp4" type="video/mp4"> + <source src="../media/echo-hereweare.webm" type="video/webm"> + <track kind="subtitles" src="../media/mediaelement.srt" srclang="en" /> + <p>Your browser leaves much to be desired.</p> +</video> + +<script> +$('audio,video').mediaelementplayer({ + // auto-select this language (instead of starting with "None") + startLanguage:'en', + // automatically translate into these languages + translations:['es','ar','zh','ru'], + // enable the dropdown list of languages + translationSelector: true +}); +</script> + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer-youtube.html b/js/mediaelement/demo/mediaelementplayer-youtube.html new file mode 100644 index 0000000000000000000000000000000000000000..627d30e1fa7404e83fe56e5faa93523af5708e93 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer-youtube.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement - YouTube</title> + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<h2>YouTube Wrapper</h2> + +<code><pre> +<video width="640" height="360" id="player1" preload="none"> + + <!-- Pseudo HTML5 --> + <source type="video/youtube" src="http://www.youtube.com/watch?v=nOEw9iiopwI" /> + +</video> +</pre></code> + +<video width="640" height="360" id="player1"> + + <!-- Pseudo HTML5 --> + <source type="video/youtube" src="http://www.youtube.com/watch?v=nOEw9iiopwI" /> + +</video> + +<span id="player1-mode"></span> + +<script> + +$('video').mediaelementplayer({ + success: function(media, node, player) { + $('#' + node.id + '-mode').html('mode: ' + media.pluginType); + } +}); + +</script> + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/mediaelementplayer.html b/js/mediaelement/demo/mediaelementplayer.html new file mode 100644 index 0000000000000000000000000000000000000000..ac7100b51d68b8a30f6a8340e6b98f76d242da66 --- /dev/null +++ b/js/mediaelement/demo/mediaelementplayer.html @@ -0,0 +1,97 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>HTML5 MediaElement</title> + + <script src="../build/jquery.js"></script> + <script src="../build/mediaelement-and-player.min.js"></script> + <script src="testforfiles.js"></script> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> +</head> +<body> + +<h1>MediaElementPlayer.js</h1> + +<h2>1. Single MP4 File</h2> + +<code><pre> +<video width="640" height="360" src="../media/echo-hereweare.mp4" type="video/mp4" + id="player1" poster="../media/echo-hereweare.jpg" + controls="controls" preload="none"></video> +</pre></code> + +<!-- simple single file method --> +<video width="640" height="360" src="../media/echo-hereweare.mp4" type="video/mp4" + id="player1" poster="../media/echo-hereweare.jpg" + controls="controls" preload="none"></video> + +<span id="player1-mode"></span> + +<p><b>Note</b>: if you are working with local files, you'll need to add your working directory +to the <a href="http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html">Flash Global Security Settings</a>.</p> + +<h2>2. Multi-Codec with no JavaScript fallback player</h2> + +<code><pre> +<video width="640" height="360" id="player2" poster="../media/echo-hereweare.jpg" controls="controls" preload="none"> + <!-- MP4 source must come first for iOS --> + <source type="video/mp4" src="../media/echo-hereweare.mp4" /> + <!-- WebM for Firefox 4 and Opera --> + <source type="video/webm" src="../media/echo-hereweare.webm" /> + <!-- OGG for Firefox 3 --> + <source type="video/ogg" src="../media/echo-hereweare.ogv" /> + <!-- Fallback flash player for no-HTML5 browsers with JavaScript turned off --> + <object width="640" height="360" type="application/x-shockwave-flash" data="../build/flashmediaelement.swf"> + <param name="movie" value="../build/flashmediaelement.swf" /> + <param name="flashvars" value="controls=true&poster=../media/echo-hereweare.jpg&file=../media/echo-hereweare.mp4" /> + <!-- Image fall back for non-HTML5 browser with JavaScript turned off and no Flash player installed --> + <img src="../media/echo-hereweare.jpg" width="640" height="360" alt="Here we are" + title="No video playback capabilities" /> + </object> +</video> +</pre></code> + +<video width="640" height="360" id="player2" poster="../media/echo-hereweare.jpg" controls="controls" preload="none"> + <!-- MP4 source must come first for iOS --> + <source type="video/mp4" src="../media/echo-hereweare.mp4" /> + <!-- WebM for Firefox 4 and Opera --> + <source type="video/webm" src="../media/echo-hereweare.webm" /> + <!-- OGG for Firefox 3 --> + <source type="video/ogg" src="../media/echo-hereweare.ogv" /> + <!-- Fallback flash player for no-HTML5 browsers with JavaScript turned off --> + <object width="640" height="360" type="application/x-shockwave-flash" data="../build/flashmediaelement.swf"> + <param name="movie" value="../build/flashmediaelement.swf" /> + <param name="flashvars" value="controls=true&file=../media/echo-hereweare.mp4" /> + <!-- Image fall back for non-HTML5 browser with JavaScript turned off and no Flash player installed --> + <img src="../media/echo-hereweare.jpg" width="640" height="360" alt="Here we are" + title="No video playback capabilities" /> + </object> +</video> + +<span id="player2-mode"></span> + +<script> + +$('audio,video').mediaelementplayer({ + mode: 'shim', + success: function(player, node) { + $('#' + node.id + '-mode').html('mode: ' + player.pluginType); + } +}); + +</script> + +<input type="button" id="stopall" value="Stop all"> + +<script> +$('#stopall').click(function() { + $('video, audio').each(function() { + $(this)[0].player.pause(); + }); +}); +</script> + + +</body> +</html> \ No newline at end of file diff --git a/js/mediaelement/demo/testforfiles.js b/js/mediaelement/demo/testforfiles.js new file mode 100644 index 0000000000000000000000000000000000000000..67d79a96f48d2ca3b7c090be9180df4734284c69 --- /dev/null +++ b/js/mediaelement/demo/testforfiles.js @@ -0,0 +1,13 @@ +// check for missing media files and warn to download + +$(function() { + + $.ajax({ + url: '../media/echo-hereweare.webm', + success: function() {}, + error: function(e) { + $(document.body).prepend($('<div style="margin: 20px; padding: 10px; color: #fff; background: #ff4444; font-family: Helvetica, sanserif;">This demo will not function properly without media files (MP4, WebM, and MP3).<br><br>Please download sample media files from <a style="color:#fff;font-weight:bold" href="https://github.com/johndyer/mediaelement-files/">https://github.com/johndyer/mediaelement-files/</a>.</div>')); + } + }); + +}); \ No newline at end of file diff --git a/js/mediaelement/media/echo-hereweare.jpg b/js/mediaelement/media/echo-hereweare.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d1c18241db9dd87efebe9aca1f637f5138a72b32 Binary files /dev/null and b/js/mediaelement/media/echo-hereweare.jpg differ diff --git a/js/mediaelement/media/echo-hereweare.mp4 b/js/mediaelement/media/echo-hereweare.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..8c9b201d578870c218c46d7fd8d541edb3a6a106 Binary files /dev/null and b/js/mediaelement/media/echo-hereweare.mp4 differ diff --git a/js/mediaelement/media/mediaelement.srt b/js/mediaelement/media/mediaelement.srt new file mode 100644 index 0000000000000000000000000000000000000000..a531b9c5a80938e61c22127653a1cd734978fc6a --- /dev/null +++ b/js/mediaelement/media/mediaelement.srt @@ -0,0 +1,61 @@ +0 +00:00:00,1 --> 00:00:04 +HTML5 <video> and <audio> was supposed to be awesome, powerful, and fun. + +1 +00:00:04 --> 00:00:07 +But browser vendors couldn't agree on a codec + +2 +00:00:07 --> 00:00:10 +and older browsers don't support <video> at all. + +3 +00:00:10 --> 00:00:12 +This means <video src="myfile.mp4" /> doesn't work ... + +4 +00:00:12 --> 00:00:14 +until now. + +5 +00:00:14 --> 00:00:18 +Introducing MediaElement.js, an HTML5 <video> and <audio> player + +6 +00:00:18 --> 00:00:21 +that looks and works the same in every browser (even iPhone and Android). + +7 +00:00:21 --> 00:00:24 +For older browsers, it has custom Flash and Silverlight plugins + +8 +00:00:24 --> 00:00:27 +that fully replicate the HTML5 MediaElement API + +9 +00:00:27 --> 00:00:30 +so you can build a consistent control UI using just HTML and CSS. + +10 +00:00:30 --> 00:00:33 +MediaElement.js even supports newer standards + +11 +00:00:33 --> 00:00:36 +like the <track> element that enables the subtitles you're reading right now. + +12 +00:00:36 --> 00:00:39 +The subtitles can even be translated into any language using Google's Translation API. + +13 +00:00:39 --> 00:00:42 +As a bonus, the Flash and Silverlight fallbacks allow you to use FLV and WMV files. + +14 +00:00:42 --> 00:00:45 +Hope you like it. + +Come follow me at <a href="http://twitter.com/johndyer">twitter.com/johndyer</a> \ No newline at end of file diff --git a/js/mediaelement/media/other.mp4 b/js/mediaelement/media/other.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..0e90ccfa7121e01f258dc08c49f64f213f56c418 Binary files /dev/null and b/js/mediaelement/media/other.mp4 differ diff --git a/js/mediaelement/package.json b/js/mediaelement/package.json new file mode 100644 index 0000000000000000000000000000000000000000..c3a6aed0cccf3ff52e0ddd685ddd751b7b387acc --- /dev/null +++ b/js/mediaelement/package.json @@ -0,0 +1,19 @@ +{ + "name": "mediaelement", + "version": "2.14.0", + "repository": { + "type": "git", + "url": "https://github.com/johndyer/mediaelement.git" + }, + "devDependencies": { + "grunt": "~0.4.1", + "grunt-contrib-uglify": "~0.2.2", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-cssmin": "~0.6.1", + "grunt-contrib-copy": "~0.4.1", + "grunt-shell": "~1.1.1", + "grunt-text-replace": "~0.3.12", + "grunt-contrib-clean": "~0.6.0", + "grunt-remove-logging": "~0.2.0" + } +} \ No newline at end of file diff --git a/js/mediaelement/src/css/background.png b/js/mediaelement/src/css/background.png new file mode 100644 index 0000000000000000000000000000000000000000..fd428412ae26af13dab448ec833b1cb603e37ee9 Binary files /dev/null and b/js/mediaelement/src/css/background.png differ diff --git a/js/mediaelement/src/css/bigplay.fw.png b/js/mediaelement/src/css/bigplay.fw.png new file mode 100644 index 0000000000000000000000000000000000000000..66d0e3cb73ceec0b1ffc7cdd4c1bc5edaf960556 Binary files /dev/null and b/js/mediaelement/src/css/bigplay.fw.png differ diff --git a/js/mediaelement/src/css/bigplay.png b/js/mediaelement/src/css/bigplay.png new file mode 100644 index 0000000000000000000000000000000000000000..694553e31c387188b6bde397a5200c212aff2dc5 Binary files /dev/null and b/js/mediaelement/src/css/bigplay.png differ diff --git a/js/mediaelement/src/css/bigplay.svg b/js/mediaelement/src/css/bigplay.svg new file mode 100644 index 0000000000000000000000000000000000000000..2b7817005d90d5b2357616d7dd210c14819d2178 --- /dev/null +++ b/js/mediaelement/src/css/bigplay.svg @@ -0,0 +1,14 @@ +<?xml version="1.0" standalone="no"?> +<svg id="bigplay" viewBox="0 0 100 200" style="background-color:#ffffff00" version="1.1" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" + x="0px" y="0px" width="100px" height="200px" +> + <g id="dark"> + <path id="Polygon" d="M 72.5 49.5 L 38.75 68.9856 L 38.75 30.0144 L 72.5 49.5 Z" fill="#ffffff" opacity="0.75" /> + <path id="Ellipse" d="M 13 50.5 C 13 29.7891 29.7891 13 50.5 13 C 71.2109 13 88 29.7891 88 50.5 C 88 71.2109 71.2109 88 50.5 88 C 29.7891 88 13 71.2109 13 50.5 Z" stroke="#ffffff" stroke-width="5" fill="none" opacity="0.75"/> + </g> + <g id="light"> + <path id="Polygon2" d="M 72.5 149.5 L 38.75 168.9856 L 38.75 130.0144 L 72.5 149.5 Z" fill="#ffffff" opacity="1.0" /> + <path id="Ellipse2" d="M 13 150.5 C 13 129.7891 29.7891 113 50.5 113 C 71.2109 113 88 129.7891 88 150.5 C 88 171.211 71.2109 188 50.5 188 C 29.7891 188 13 171.211 13 150.5 Z" stroke="#ffffff" stroke-width="5" fill="none" opacity="1.0"/> + </g> +</svg> \ No newline at end of file diff --git a/js/mediaelement/src/css/controls-ted.png b/js/mediaelement/src/css/controls-ted.png new file mode 100644 index 0000000000000000000000000000000000000000..3aac05aa83cb7fed54831a19d85a8c267e939720 Binary files /dev/null and b/js/mediaelement/src/css/controls-ted.png differ diff --git a/js/mediaelement/src/css/controls-wmp-bg.png b/js/mediaelement/src/css/controls-wmp-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..89bb9b95602ecfca6290f6006f817da365da7b90 Binary files /dev/null and b/js/mediaelement/src/css/controls-wmp-bg.png differ diff --git a/js/mediaelement/src/css/controls-wmp.png b/js/mediaelement/src/css/controls-wmp.png new file mode 100644 index 0000000000000000000000000000000000000000..4775ef5b02faf2b826d35dfc72511b6b27fea87b Binary files /dev/null and b/js/mediaelement/src/css/controls-wmp.png differ diff --git a/js/mediaelement/src/css/controls.fw.png b/js/mediaelement/src/css/controls.fw.png new file mode 100644 index 0000000000000000000000000000000000000000..e27682ae107408748ac91805eee0e1554d618cd5 Binary files /dev/null and b/js/mediaelement/src/css/controls.fw.png differ diff --git a/js/mediaelement/src/css/controls.png b/js/mediaelement/src/css/controls.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a857d800b64264443af4609e0ebf7175593d8f Binary files /dev/null and b/js/mediaelement/src/css/controls.png differ diff --git a/js/mediaelement/src/css/controls.svg b/js/mediaelement/src/css/controls.svg new file mode 100644 index 0000000000000000000000000000000000000000..af3bd41606ae3f0f8d20d213f9e8819569bb7715 --- /dev/null +++ b/js/mediaelement/src/css/controls.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?> <!-- Generator: Adobe Fireworks CS6, Export SVG Extension by Aaron Beall (http://fireworks.abeall.com) . Version: 0.6.1 --> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg id="controls.fw-Page%201" viewBox="0 0 144 32" style="background-color:#ffffff00" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" x="0px" y="0px" width="144px" height="32px" > <defs> <radialGradient id="gradient1" cx="50%" cy="50%" r="50%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#f2f2f2" stop-opacity="0.2" offset="100%"/> </radialGradient> <linearGradient id="gradient2" x1="50%" y1="-7.8652%" x2="50%" y2="249.6629%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient3" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient4" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient5" x1="50%" y1="-33.3333%" x2="50%" y2="152.0833%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient6" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient7" x1="50%" y1="-33.3333%" x2="50%" y2="152.0833%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient8" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient9" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient10" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient11" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient12" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient13" x1="40%" y1="-140%" x2="40%" y2="98.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient14" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient15" x1="60%" y1="-140%" x2="60%" y2="98.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient16" x1="50%" y1="0%" x2="50%" y2="298.4375%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient17" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient18" x1="50%" y1="-200%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient19" x1="50%" y1="-200%" x2="50%" y2="110.9375%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient20" x1="55%" y1="0%" x2="55%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient21" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="99.4444%"/> </linearGradient> </defs> <g id="BG"> </g> <g id="controls"> <path id="Line" d="M 98.5 7.5 L 109.5 7.5 " stroke="#ffffff" stroke-width="1" fill="none"/> <path id="Line2" d="M 98.5 3.5 L 109.5 3.5 " stroke="#ffffff" stroke-width="1" fill="none"/> <path id="Line3" d="M 98.5 11.5 L 109.5 11.5 " stroke="#ffffff" stroke-width="1" fill="none"/> <path id="Ellipse" d="M 108 11.5 C 108 10.6716 108.4477 10 109 10 C 109.5523 10 110 10.6716 110 11.5 C 110 12.3284 109.5523 13 109 13 C 108.4477 13 108 12.3284 108 11.5 Z" fill="#ffffff"/> <path id="Ellipse2" d="M 104 7.5 C 104 6.6716 104.4477 6 105 6 C 105.5523 6 106 6.6716 106 7.5 C 106 8.3284 105.5523 9 105 9 C 104.4477 9 104 8.3284 104 7.5 Z" fill="#ffffff"/> <path id="Ellipse3" d="M 108 3.5 C 108 2.6716 108.4477 2 109 2 C 109.5523 2 110 2.6716 110 3.5 C 110 4.3284 109.5523 5 109 5 C 108.4477 5 108 4.3284 108 3.5 Z" fill="#ffffff"/> </g> <g id="backlight"> <g id="off"> <rect x="83" y="21" width="10" height="6" stroke="#ffffff" stroke-width="1" fill="#333333"/> </g> <g id="on"> <path id="Ellipse4" d="M 81 8 C 81 5.2385 84.134 3 88 3 C 91.866 3 95 5.2385 95 8 C 95 10.7615 91.866 13 88 13 C 84.134 13 81 10.7615 81 8 Z" fill="url(#gradient1)"/> <rect x="83" y="5" width="10" height="6" stroke="#ffffff" stroke-width="1" fill="#333333"/> </g> </g> <g id="loop"> <g id="on2"> <path d="M 73.795 4.205 C 75.2155 4.8785 76.2 6.3234 76.2 8 C 76.2 10.3196 74.3196 12.2 72 12.2 C 69.6804 12.2 67.8 10.3196 67.8 8 C 67.8 6.3234 68.7845 4.8785 70.205 4.205 L 68.875 2.875 C 67.1501 3.9289 66 5.8306 66 8 C 66 11.3138 68.6862 14 72 14 C 75.3138 14 78 11.3138 78 8 C 78 5.8306 76.8499 3.9289 75.125 2.875 L 73.795 4.205 Z" fill="url(#gradient2)"/> <path d="M 71 2 L 66 2 L 71 7 L 71 2 Z" fill="url(#gradient3)"/> </g> <g id="off2"> <path d="M 73.795 20.205 C 75.2155 20.8785 76.2 22.3234 76.2 24 C 76.2 26.3196 74.3196 28.2 72 28.2 C 69.6804 28.2 67.8 26.3196 67.8 24 C 67.8 22.3234 68.7845 20.8785 70.205 20.205 L 68.875 18.875 C 67.1501 19.9289 66 21.8306 66 24 C 66 27.3138 68.6862 30 72 30 C 75.3138 30 78 27.3138 78 24 C 78 21.8306 76.8499 19.9289 75.125 18.875 L 73.795 20.205 Z" fill="#a8a8b7"/> <path d="M 71 18 L 66 18 L 71 23 L 71 18 Z" fill="#a8a8b7"/> </g> </g> <g id="cc"> <rect visibility="hidden" x="49" y="2" width="14" height="12" stroke="#b0b0b0" stroke-width="1" fill="none"/> <text visibility="hidden" x="49" y="17" width="14" fill="#ffffff" style="font-size: 10px; color: #ffffff; font-family: Arial; text-align: center; "><tspan><![CDATA[cc]]></tspan></text> <path d="M 55 7 C 50.2813 3.7813 50.063 12.9405 55 10 " stroke="#ffffff" stroke-width="1" fill="none"/> <path d="M 60 7 C 55.2813 3.7813 55.063 12.9405 60 10 " stroke="#ffffff" stroke-width="1" fill="none"/> <path d="M 50 3 L 62 3 L 62 13 L 50 13 L 50 3 ZM 49 2 L 49 14 L 63 14 L 63 2 L 49 2 Z" fill="url(#gradient4)"/> <rect x="49" y="2" width="14" height="12" fill="none"/> </g> <g id="volume"> <g id="no%20sound"> <rect x="17" y="5" width="5" height="6" fill="url(#gradient5)"/> <path d="M 21 5 L 25 2 L 25 14 L 21 11.0625 L 21 5 Z" fill="url(#gradient6)"/> </g> <g id="sound%20bars"> <rect x="17" y="21" width="5" height="6" fill="url(#gradient7)"/> <path d="M 21 21 L 25 18 L 25 30 L 21 27.0625 L 21 21 Z" fill="url(#gradient8)"/> <path d="M 27 18 C 27 18 30.0625 17.375 30 24 C 29.9375 30.625 27 30 27 30 " stroke="#ffffff" stroke-width="1" fill="none"/> <path d="M 26 21.0079 C 26 21.0079 28.041 20.6962 27.9994 24 C 27.9577 27.3038 26 26.9921 26 26.9921 " stroke="#ffffff" stroke-width="1" fill="none"/> </g> </g> <g id="play/pause"> <g id="play"> <path id="Polygon" d="M 14 8.5 L 3 14 L 3 3 L 14 8.5 Z" fill="url(#gradient9)"/> </g> <g id="pause"> <rect x="3" y="18" width="3" height="12" fill="url(#gradient10)"/> <rect x="10" y="18" width="3" height="12" fill="url(#gradient11)"/> </g> </g> <g id="fullscreen"> <g id="enter%201"> <path d="M 34 2 L 39 2 L 34 7 L 34 2 Z" fill="url(#gradient12)"/> <path d="M 34 14 L 39 14 L 34 9 L 34 14 Z" fill="url(#gradient13)"/> <path d="M 46 2 L 41 2 L 46 7 L 46 2 Z" fill="url(#gradient14)"/> <path d="M 46 14 L 41 14 L 46 9 L 46 14 Z" fill="url(#gradient15)"/> </g> <g id="exit"> <path d="M 42 22 L 46 22 L 42 18 L 42 22 Z" fill="url(#gradient16)"/> <path d="M 38 22 L 38 18 L 34 22 L 38 22 Z" fill="url(#gradient17)"/> <path d="M 38 26 L 34 26 L 38 30 L 38 26 Z" fill="url(#gradient18)"/> <path d="M 42 26 L 42 30 L 46 26 L 42 26 Z" fill="url(#gradient19)"/> </g> </g> <g id="stop"> <rect x="115" y="3" width="10" height="10" fill="url(#gradient20)"/> </g> <g id="chooser"> <path d="M 135.2346 6.1522 C 136.2551 5.7295 137.4251 6.2141 137.8478 7.2346 C 138.2704 8.2551 137.7859 9.425 136.7654 9.8478 C 135.7449 10.2705 134.5749 9.7859 134.1522 8.7654 C 133.7295 7.7449 134.2141 6.5749 135.2346 6.1522 ZM 133.2735 1.4176 L 136 4.0054 L 138.7265 1.4176 L 138.8246 5.1754 L 142.5824 5.2735 L 139.9946 8 L 142.5824 10.7265 L 138.8246 10.8246 L 138.7265 14.5824 L 136 11.9946 L 133.2735 14.5824 L 133.1754 10.8246 L 129.4176 10.7265 L 132.0054 8 L 129.4176 5.2735 L 133.1754 5.1754 L 133.2735 1.4176 Z" fill="url(#gradient21)"/> </g> </svg> \ No newline at end of file diff --git a/js/mediaelement/src/css/loading.gif b/js/mediaelement/src/css/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..612222be5e474c36c345042dd6f697fa1d16a6a0 Binary files /dev/null and b/js/mediaelement/src/css/loading.gif differ diff --git a/js/mediaelement/src/css/mediaelementplayer.css b/js/mediaelement/src/css/mediaelementplayer.css new file mode 100644 index 0000000000000000000000000000000000000000..432ef5c4e966f3a43ef4dbca9e4a448a7ede764a --- /dev/null +++ b/js/mediaelement/src/css/mediaelementplayer.css @@ -0,0 +1,954 @@ +.mejs-container { + position: relative; + background: #000; + font-family: Helvetica, Arial; + text-align: left; + vertical-align: top; + text-indent: 0; +} + +.me-plugin { + position: absolute; + height: auto; + width: auto; +} + +.mejs-embed, .mejs-embed body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + background: #000; + overflow: hidden; +} + +.mejs-fullscreen { + /* set it to not show scroll bars so 100% will work */ + overflow: hidden !important; +} + +.mejs-container-fullscreen { + position: fixed; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + z-index: 1000; +} +.mejs-container-fullscreen .mejs-mediaelement, +.mejs-container-fullscreen video { + width: 100%; + height: 100%; +} + +.mejs-clear { + clear: both; +} + +/* Start: LAYERS */ +.mejs-background { + position: absolute; + top: 0; + left: 0; +} + +.mejs-mediaelement { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.mejs-poster { + position: absolute; + top: 0; + left: 0; + background-size: contain ; + background-position: 50% 50% ; + background-repeat: no-repeat ; +} +:root .mejs-poster img { + display: none ; +} + +.mejs-poster img { + border: 0; + padding: 0; + border: 0; +} + +.mejs-overlay { + position: absolute; + top: 0; + left: 0; +} + +.mejs-overlay-play { + cursor: pointer; +} + +.mejs-overlay-button { + position: absolute; + top: 50%; + left: 50%; + width: 100px; + height: 100px; + margin: -50px 0 0 -50px; + background: url(bigplay.svg) no-repeat; +} + +.no-svg .mejs-overlay-button { + background-image: url(bigplay.png); +} + +.mejs-overlay:hover .mejs-overlay-button { + background-position: 0 -100px ; +} + +.mejs-overlay-loading { + position: absolute; + top: 50%; + left: 50%; + width: 80px; + height: 80px; + margin: -40px 0 0 -40px; + background: #333; + background: url(background.png); + background: rgba(0, 0, 0, 0.9); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.9)), to(rgba(0,0,0,0.9))); + background: -webkit-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: -moz-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: -o-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: -ms-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: linear-gradient(rgba(50,50,50,0.9), rgba(0,0,0,0.9)); +} + +.mejs-overlay-loading span { + display: block; + width: 80px; + height: 80px; + background: transparent url(loading.gif) 50% 50% no-repeat; +} + +/* End: LAYERS */ + +/* Start: CONTROL BAR */ +.mejs-container .mejs-controls { + position: absolute; + list-style-type: none; + margin: 0; + padding: 0; + bottom: 0; + left: 0; + background: url(background.png); + background: rgba(0, 0, 0, 0.7); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.7)), to(rgba(0,0,0,0.7))); + background: -webkit-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -moz-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -o-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -ms-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: linear-gradient(rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + height: 30px; + width: 100%; +} +.mejs-container .mejs-controls div { + list-style-type: none; + background-image: none; + display: block; + float: left; + margin: 0; + padding: 0; + width: 26px; + height: 26px; + font-size: 11px; + line-height: 11px; + font-family: Helvetica, Arial; + border: 0; +} + +.mejs-controls .mejs-button button { + cursor: pointer; + display: block; + font-size: 0; + line-height: 0; + text-decoration: none; + margin: 7px 5px; + padding: 0; + position: absolute; + height: 16px; + width: 16px; + border: 0; + background: transparent url(controls.svg) no-repeat; +} + +.no-svg .mejs-controls .mejs-button button { + background-image: url(controls.png); +} + +/* :focus for accessibility */ +.mejs-controls .mejs-button button:focus { + outline: dotted 1px #999; +} + +/* End: CONTROL BAR */ + +/* Start: Time (Current / Duration) */ +.mejs-container .mejs-controls .mejs-time { + color: #fff; + display: block; + height: 17px; + width: auto; + padding: 8px 3px 0 3px ; + overflow: hidden; + text-align: center; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.mejs-container .mejs-controls .mejs-time span { + color: #fff; + font-size: 11px; + line-height: 12px; + display: block; + float: left; + margin: 1px 2px 0 0; + width: auto; +} +/* End: Time (Current / Duration) */ + +/* Start: Play/Pause/Stop */ +.mejs-controls .mejs-play button { + background-position: 0 0; +} + +.mejs-controls .mejs-pause button { + background-position: 0 -16px; +} + +.mejs-controls .mejs-stop button { + background-position: -112px 0; +} +/* Start: Play/Pause/Stop */ + +/* Start: Progress Bar */ +.mejs-controls div.mejs-time-rail { + direction: ltr; + width: 200px; + padding-top: 5px; +} + +.mejs-controls .mejs-time-rail span { + display: block; + position: absolute; + width: 180px; + height: 10px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + cursor: pointer; +} + +.mejs-controls .mejs-time-rail .mejs-time-total { + margin: 5px; + background: #333; + background: rgba(50,50,50,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(30,30,30,0.8)), to(rgba(60,60,60,0.8))); + background: -webkit-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -moz-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -o-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -ms-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: linear-gradient(rgba(30,30,30,0.8), rgba(60,60,60,0.8)); +} + +.mejs-controls .mejs-time-rail .mejs-time-buffering { + width: 100%; + background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 15px 15px; + -moz-background-size: 15px 15px; + -o-background-size: 15px 15px; + background-size: 15px 15px; + -webkit-animation: buffering-stripes 2s linear infinite; + -moz-animation: buffering-stripes 2s linear infinite; + -ms-animation: buffering-stripes 2s linear infinite; + -o-animation: buffering-stripes 2s linear infinite; + animation: buffering-stripes 2s linear infinite; +} + +@-webkit-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@-moz-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@-ms-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@-o-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } + +.mejs-controls .mejs-time-rail .mejs-time-loaded { + background: #3caac8; + background: rgba(60,170,200,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(44,124,145,0.8)), to(rgba(78,183,212,0.8))); + background: -webkit-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: -moz-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: -o-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: -ms-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: linear-gradient(rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + width: 0; +} + +.mejs-controls .mejs-time-rail .mejs-time-current { + background: #fff; + background: rgba(255,255,255,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,0.9)), to(rgba(200,200,200,0.8))); + background: -webkit-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -moz-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -o-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -ms-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: linear-gradient(rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + width: 0; +} + +.mejs-controls .mejs-time-rail .mejs-time-handle { + display: none; + position: absolute; + margin: 0; + width: 10px; + background: #fff; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + cursor: pointer; + border: solid 2px #333; + top: -2px; + text-align: center; +} + +.mejs-controls .mejs-time-rail .mejs-time-float { + position: absolute; + display: none; + background: #eee; + width: 36px; + height: 17px; + border: solid 1px #333; + top: -26px; + margin-left: -18px; + text-align: center; + color: #111; +} + +.mejs-controls .mejs-time-rail .mejs-time-float-current { + margin: 2px; + width: 30px; + display: block; + text-align: center; + left: 0; +} + +.mejs-controls .mejs-time-rail .mejs-time-float-corner { + position: absolute; + display: block; + width: 0; + height: 0; + line-height: 0; + border: solid 5px #eee; + border-color: #eee transparent transparent transparent; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + top: 15px; + left: 13px; +} + +.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float { + width: 48px; +} + +.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float-current { + width: 44px; +} + +.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float-corner { + left: 18px; +} + +/* +.mejs-controls .mejs-time-rail:hover .mejs-time-handle { + visibility:visible; +} +*/ +/* End: Progress Bar */ + +/* Start: Fullscreen */ +.mejs-controls .mejs-fullscreen-button button { + background-position: -32px 0; +} + +.mejs-controls .mejs-unfullscreen button { + background-position: -32px -16px; +} +/* End: Fullscreen */ + + +/* Start: Mute/Volume */ +.mejs-controls .mejs-volume-button { +} + +.mejs-controls .mejs-mute button { + background-position: -16px -16px; +} + +.mejs-controls .mejs-unmute button { + background-position: -16px 0; +} + +.mejs-controls .mejs-volume-button { + position: relative; +} + +.mejs-controls .mejs-volume-button .mejs-volume-slider { + display: none; + height: 115px; + width: 25px; + background: url(background.png); + background: rgba(50, 50, 50, 0.7); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + top: -115px; + left: 0; + z-index: 1; + position: absolute; + margin: 0; +} + +.mejs-controls .mejs-volume-button:hover { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +/* +.mejs-controls .mejs-volume-button:hover .mejs-volume-slider { + display: block; +} +*/ + +.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-total { + position: absolute; + left: 11px; + top: 8px; + width: 2px; + height: 100px; + background: #ddd; + background: rgba(255, 255, 255, 0.5); + margin: 0; +} + +.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-current { + position: absolute; + left: 11px; + top: 8px; + width: 2px; + height: 100px; + background: #ddd; + background: rgba(255, 255, 255, 0.9); + margin: 0; +} + +.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-handle { + position: absolute; + left: 4px; + top: -3px; + width: 16px; + height: 6px; + background: #ddd; + background: rgba(255, 255, 255, 0.9); + cursor: N-resize; + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; + margin: 0; +} + +/* horizontal version */ +.mejs-controls div.mejs-horizontal-volume-slider { + height: 26px; + width: 60px; + position: relative; +} + +.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-total { + position: absolute; + left: 0; + top: 11px; + width: 50px; + height: 8px; + margin: 0; + padding: 0; + font-size: 1px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + background: #333; + background: rgba(50,50,50,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(30,30,30,0.8)), to(rgba(60,60,60,0.8))); + background: -webkit-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -moz-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -o-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -ms-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: linear-gradient(rgba(30,30,30,0.8), rgba(60,60,60,0.8)); +} + +.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-current { + position: absolute; + left: 0; + top: 11px; + width: 50px; + height: 8px; + margin: 0; + padding: 0; + font-size: 1px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + background: #fff; + background: rgba(255,255,255,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,0.9)), to(rgba(200,200,200,0.8))); + background: -webkit-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -moz-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -o-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -ms-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: linear-gradient(rgba(255,255,255,0.9), rgba(200,200,200,0.8)); +} + +.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-handle { + display: none; +} + +/* End: Mute/Volume */ + +/* Start: Track (Captions and Chapters) */ +.mejs-controls .mejs-captions-button { + position: relative; +} + +.mejs-controls .mejs-captions-button button { + background-position: -48px 0; +} +.mejs-controls .mejs-captions-button .mejs-captions-selector { + visibility: hidden; + position: absolute; + bottom: 26px; + right: -51px; + width: 85px; + height: 100px; + background: url(background.png); + background: rgba(50,50,50,0.7); + border: solid 1px transparent; + padding: 10px 10px 0 10px; + overflow: hidden; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +/* +.mejs-controls .mejs-captions-button:hover .mejs-captions-selector { + visibility: visible; +} +*/ + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul { + margin: 0; + padding: 0; + display: block; + list-style-type: none !important; + overflow: hidden; +} + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul li { + margin: 0 0 6px 0; + padding: 0; + list-style-type: none !important; + display: block; + color: #fff; + overflow: hidden; +} + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul li input { + clear: both; + float: left; + margin: 3px 3px 0 5px; +} + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul li label { + width: 55px; + float: left; + padding: 4px 0 0 0; + line-height: 15px; + font-family: helvetica, arial; + font-size: 10px; +} + +.mejs-controls .mejs-captions-button .mejs-captions-translations { + font-size: 10px; + margin: 0 0 5px 0; +} + +.mejs-chapters { + position: absolute; + top: 0; + left: 0; + -xborder-right: solid 1px #fff; + width: 10000px; + z-index: 1; +} + +.mejs-chapters .mejs-chapter { + position: absolute; + float: left; + background: #222; + background: rgba(0, 0, 0, 0.7); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.7)), to(rgba(0,0,0,0.7))); + background: -webkit-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -moz-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -o-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -ms-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: linear-gradient(rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, startColorstr=#323232,endColorstr=#000000); + overflow: hidden; + border: 0; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block { + font-size: 11px; + color: #fff; + padding: 5px; + display: block; + border-right: solid 1px #333; + border-bottom: solid 1px #333; + cursor: pointer; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block-last { + border-right: none; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block:hover { + background: #666; + background: rgba(102,102,102, 0.7); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(102,102,102,0.7)), to(rgba(50,50,50,0.6))); + background: -webkit-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: -moz-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: -o-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: -ms-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: linear-gradient(rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, startColorstr=#666666,endColorstr=#323232); +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block .ch-title { + font-size: 12px; + font-weight: bold; + display: block; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0 0 3px 0; + line-height: 12px; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block .ch-timespan { + font-size: 12px; + line-height: 12px; + margin: 3px 0 4px 0; + display: block; + white-space: nowrap; + text-overflow: ellipsis; +} + +.mejs-captions-layer { + position: absolute; + bottom: 0; + left: 0; + text-align:center; + line-height: 20px; + font-size: 16px; + color: #fff; +} + +.mejs-captions-layer a { + color: #fff; + text-decoration: underline; +} + +.mejs-captions-layer[lang=ar] { + font-size: 20px; + font-weight: normal; +} + +.mejs-captions-position { + position: absolute; + width: 100%; + bottom: 15px; + left: 0; +} + +.mejs-captions-position-hover { + bottom: 35px; +} + +.mejs-captions-text { + padding: 3px 5px; + background: url(background.png); + background: rgba(20, 20, 20, 0.5); + white-space: pre-wrap; +} +/* End: Track (Captions and Chapters) */ + +/* Start: Error */ +.me-cannotplay { +} + +.me-cannotplay a { + color: #fff; + font-weight: bold; +} + +.me-cannotplay span { + padding: 15px; + display: block; +} +/* End: Error */ + + +/* Start: Loop */ +.mejs-controls .mejs-loop-off button { + background-position: -64px -16px; +} + +.mejs-controls .mejs-loop-on button { + background-position: -64px 0; +} + +/* End: Loop */ + +/* Start: backlight */ +.mejs-controls .mejs-backlight-off button { + background-position: -80px -16px; +} + +.mejs-controls .mejs-backlight-on button { + background-position: -80px 0; +} +/* End: backlight */ + +/* Start: Picture Controls */ +.mejs-controls .mejs-picturecontrols-button { + background-position: -96px 0; +} +/* End: Picture Controls */ + + +/* context menu */ +.mejs-contextmenu { + position: absolute; + width: 150px; + padding: 10px; + border-radius: 4px; + top: 0; + left: 0; + background: #fff; + border: solid 1px #999; + z-index: 1001; /* make sure it shows on fullscreen */ +} +.mejs-contextmenu .mejs-contextmenu-separator { + height: 1px; + font-size: 0; + margin: 5px 6px; + background: #333; +} + +.mejs-contextmenu .mejs-contextmenu-item { + font-family: Helvetica, Arial; + font-size: 12px; + padding: 4px 6px; + cursor: pointer; + color: #333; +} +.mejs-contextmenu .mejs-contextmenu-item:hover { + background: #2C7C91; + color: #fff; +} + +/* Start: Source Chooser */ +.mejs-controls .mejs-sourcechooser-button { + position: relative; +} + +.mejs-controls .mejs-sourcechooser-button button { + background-position: -128px 0; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector { + visibility: hidden; + position: absolute; + bottom: 26px; + right: -10px; + width: 130px; + height: 100px; + background: url(background.png); + background: rgba(50,50,50,0.7); + border: solid 1px transparent; + padding: 10px; + overflow: hidden; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul { + margin: 0; + padding: 0; + display: block; + list-style-type: none !important; + overflow: hidden; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li { + margin: 0 0 6px 0; + padding: 0; + list-style-type: none !important; + display: block; + color: #fff; + overflow: hidden; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li input { + clear: both; + float: left; + margin: 3px 3px 0 5px; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li label { + width: 100px; + float: left; + padding: 4px 0 0 0; + line-height: 15px; + font-family: helvetica, arial; + font-size: 10px; +} +/* End: Source Chooser */ + +/* Start: Postroll */ +.mejs-postroll-layer { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + background: url(background.png); + background: rgba(50,50,50,0.7); + z-index: 1000; + overflow: hidden; +} +.mejs-postroll-layer-content { + width: 100%; + height: 100%; +} +.mejs-postroll-close { + position: absolute; + right: 0; + top: 0; + background: url(background.png); + background: rgba(50,50,50,0.7); + color: #fff; + padding: 4px; + z-index: 100; + cursor: pointer; +} +/* End: Postroll */ + + +/* Start: Speed */ +div.mejs-speed-button { + width: 46px !important; + position: relative; +} + +.mejs-controls .mejs-button.mejs-speed-button button { + background: transparent; + width: 36px; + font-size: 11px; + line-height: normal; + color: #ffffff; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector { + visibility: hidden; + position: absolute; + top: -100px; + left: -10px; + width: 60px; + height: 100px; + background: url(background.png); + background: rgba(50, 50, 50, 0.7); + border: solid 1px transparent; + padding: 0; + overflow: hidden; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.mejs-controls .mejs-speed-button:hover > .mejs-speed-selector { + visibility: visible; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li label.mejs-speed-selected { + color: rgba(33, 248, 248, 1); +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul { + margin: 0; + padding: 0; + display: block; + list-style-type: none !important; + overflow: hidden; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li { + margin: 0 0 6px 0; + padding: 0 10px; + list-style-type: none !important; + display: block; + color: #fff; + overflow: hidden; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li input { + clear: both; + float: left; + margin: 3px 3px 0 5px; + display: none; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li label { + width: 60px; + float: left; + padding: 4px 0 0 0; + line-height: 15px; + font-family: helvetica, arial; + font-size: 11.5px; + color: white; + margin-left: 5px; + cursor: pointer; +} + +.mejs-controls .mejs-speed-button .mejs-speed-selector ul li:hover { + background-color: rgb(200, 200, 200) !important; + background-color: rgba(255,255,255,.4) !important; +} +/* End: Speed */ diff --git a/js/mediaelement/src/css/mejs-skins.css b/js/mediaelement/src/css/mejs-skins.css new file mode 100644 index 0000000000000000000000000000000000000000..5c27cf156fcd04effddbd141aae1b034b6eab8ff --- /dev/null +++ b/js/mediaelement/src/css/mejs-skins.css @@ -0,0 +1,289 @@ +/* TED player */ +.mejs-container.mejs-ted { + +} +.mejs-ted .mejs-controls { + background: #eee; + height: 65px; +} + +.mejs-ted .mejs-button, +.mejs-ted .mejs-time { + position: absolute; + background: #ddd; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-total { + background-color: none; + background: url(controls-ted.png) repeat-x 0 -52px; + height: 6px; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-buffering { + height: 6px; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-loaded { + background-color: none; + background: url(controls-ted.png) repeat-x 0 -52px; + width: 0; + height: 6px; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-current { + width: 0; + height: 6px; + background-color: none; + background: url(controls-ted.png) repeat-x 0 -59px; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-handle { + display: block; + margin: 0; + width: 14px; + height: 21px; + top: -7px; + border: 0; + background: url(controls-ted.png) no-repeat 0 0; +} +.mejs-ted .mejs-controls .mejs-time-rail .mejs-time-float { + display: none; +} +.mejs-ted .mejs-controls .mejs-playpause-button { + top: 29px; + left: 9px; + width: 49px; + height: 28px; +} +.mejs-ted .mejs-controls .mejs-playpause-button button { + width: 49px; + height: 28px; + background: url(controls-ted.png) no-repeat -50px -23px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-pause button { + background-position: 0 -23px; +} + +.mejs-ted .mejs-controls .mejs-fullscreen-button { + top: 34px; + right: 9px; + width: 17px; + height: 15px; + background : none; +} +.mejs-ted .mejs-controls .mejs-fullscreen-button button { + width: 19px; + height: 17px; + background: transparent url(controls-ted.png) no-repeat 0 -66px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-unfullscreen button { + background: transparent url(controls-ted.png) no-repeat -21px -66px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-volume-button { + top: 30px; + right: 35px; + width: 24px; + height: 22px; +} +.mejs-ted .mejs-controls .mejs-mute button { + background: url(controls-ted.png) no-repeat -15px 0; + width: 24px; + height: 22px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-unmute button { + background: url(controls-ted.png) no-repeat -40px 0; + width: 24px; + height: 22px; + margin: 0; + padding: 0; +} +.mejs-ted .mejs-controls .mejs-volume-button .mejs-volume-slider { + background: #fff; + border: solid 1px #aaa; + border-width: 1px 1px 0 1px; + width: 22px; + height: 65px; + top: -65px; +} +.mejs-ted .mejs-controls .mejs-volume-button .mejs-volume-total { + background: url(controls-ted.png) repeat-y -41px -66px; + left: 8px; + width: 6px; + height: 50px; +} +.mejs-ted .mejs-controls .mejs-volume-button .mejs-volume-current { + left: 8px; + width: 6px; + background: url(controls-ted.png) repeat-y -48px -66px; + height: 50px; +} + +.mejs-ted .mejs-controls .mejs-volume-button .mejs-volume-handle { + display: none; +} + +.mejs-ted .mejs-controls .mejs-time span { + color: #333; +} +.mejs-ted .mejs-controls .mejs-currenttime-container { + position: absolute; + top: 32px; + right: 100px; + border: solid 1px #999; + background: #fff; + color: #333; + padding-top: 2px; + border-radius: 3px; + color: #333; +} +.mejs-ted .mejs-controls .mejs-duration-container { + + position: absolute; + top: 32px; + right: 65px; + border: solid 1px #999; + background: #fff; + color: #333; + padding-top: 2px; + border-radius: 3px; + color: #333; +} + +.mejs-ted .mejs-controls .mejs-time button{ + color: #333; +} +.mejs-ted .mejs-controls .mejs-captions-button { + display: none; +} +/* END: TED player */ + + +/* WMP player */ +.mejs-container.mejs-wmp { + +} +.mejs-wmp .mejs-controls { + background: transparent url(controls-wmp-bg.png) center 16px no-repeat; + height: 65px; +} + +.mejs-wmp .mejs-button, +.mejs-wmp .mejs-time { + position: absolute; + background: transparent; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-total { + background-color: transparent; + border: solid 1px #ccc; + height: 3px; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-buffering { + height: 3px; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-loaded { + background-color: rgba(255,255,255,0.3); + width: 0; + height: 3px; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-current { + width: 0; + height: 1px; + background-color: #014CB6; + border: solid 1px #7FC9FA; + border-width: 1px 0; + border-color: #7FC9FA #fff #619FF2 #fff; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-handle { + display: block; + margin: 0; + width: 16px; + height: 9px; + top: -3px; + border: 0; + background: url(controls-wmp.png) no-repeat 0 -80px; +} +.mejs-wmp .mejs-controls .mejs-time-rail .mejs-time-float { + display: none; +} +.mejs-wmp .mejs-controls .mejs-playpause-button { + top: 10px; + left: 50%; + margin: 10px 0 0 -20px; + width: 40px; + height: 40px; + +} +.mejs-wmp .mejs-controls .mejs-playpause-button button { + width: 40px; + height: 40px; + background: url(controls-wmp.png) no-repeat 0 0; + margin: 0; + padding: 0; +} +.mejs-wmp .mejs-controls .mejs-pause button { + background-position: 0 -40px; +} + +.mejs-wmp .mejs-controls .mejs-currenttime-container { + position: absolute; + top: 25px; + left: 50%; + margin-left: -93px; +} +.mejs-wmp .mejs-controls .mejs-duration-container { + position: absolute; + top: 25px; + left: 50%; + margin-left: -58px; +} + + +.mejs-wmp .mejs-controls .mejs-volume-button { + top: 32px; + right: 50%; + margin-right: -55px; + width: 20px; + height: 15px; +} +.mejs-wmp .mejs-controls .mejs-volume-button button { + margin: 0; + padding: 0; + background: url(controls-wmp.png) no-repeat -42px -17px; + width: 20px; + height: 15px; +} +.mejs-wmp .mejs-controls .mejs-unmute button { + margin: 0; + padding: 0; + background: url(controls-wmp.png) no-repeat -42px 0; + width: 20px; + height: 15px; +} +.mejs-wmp .mejs-controls .mejs-volume-button .mejs-volume-slider { + background: rgba(102,102,102,0.6); +} + +.mejs-wmp .mejs-controls .mejs-fullscreen-button { + top: 32px; + right: 50%; + margin-right: -82px; + width: 15px; + height: 14px; +} +.mejs-wmp .mejs-controls .mejs-fullscreen-button button { + margin: 0; + padding: 0; + background: url(controls-wmp.png) no-repeat -63px 0; + width: 15px; + height: 14px; +} +.mejs-wmp .mejs-controls .mejs-captions-button { + display: none; +} +/* END: WMP player */ + + + diff --git a/js/mediaelement/src/flash/FlashMediaElement.as b/js/mediaelement/src/flash/FlashMediaElement.as new file mode 100644 index 0000000000000000000000000000000000000000..d15cc456c8d7985de8dfb8d313062bd82847f6b1 --- /dev/null +++ b/js/mediaelement/src/flash/FlashMediaElement.as @@ -0,0 +1,1110 @@ +package +{ + import flash.display.*; + import flash.events.*; + import flash.media.*; + import flash.net.*; + import flash.text.*; + import flash.system.*; + + import flash.media.Video; + import flash.net.NetConnection; + import flash.net.NetStream; + + import flash.geom.ColorTransform; + + import flash.filters.DropShadowFilter; + import flash.utils.Timer; + import flash.external.ExternalInterface; + import flash.geom.Rectangle; + + import htmlelements.IMediaElement; + import htmlelements.VideoElement; + import htmlelements.AudioElement; + import htmlelements.YouTubeElement; + import htmlelements.HLSMediaElement; + + public class FlashMediaElement extends MovieClip { + + private var _mediaUrl:String; + private var _autoplay:Boolean; + private var _preload:String; + private var _debug:Boolean; + private var _isVideo:Boolean; + private var _video:DisplayObject; + private var _timerRate:Number; + private var _stageWidth:Number; + private var _stageHeight:Number; + private var _enableSmoothing:Boolean; + private var _allowedPluginDomain:String; + private var _isFullScreen:Boolean = false; + private var _startVolume:Number; + private var _controlStyle:String; + private var _autoHide:Boolean = true; + private var _streamer:String = ""; + private var _enablePseudoStreaming:Boolean; + private var _pseudoStreamingStartQueryParam:String; + + // native video size (from meta data) + private var _nativeVideoWidth:Number = 0; + private var _nativeVideoHeight:Number = 0; + + // visual elements + private var _mediaElementDisplay:FlashMediaElementDisplay = new FlashMediaElementDisplay(); + private var _output:TextField; + private var _fullscreenButton:SimpleButton; + + // media + private var _mediaElement:IMediaElement; + + // connection to fullscreen + private var _connection:LocalConnection; + private var _connectionName:String; + + //private var fullscreen_btn:SimpleButton; + + // CONTROLS + private var _alwaysShowControls:Boolean; + private var _controlBar:MovieClip; + private var _controlBarBg:MovieClip; + private var _scrubBar:MovieClip; + private var _scrubTrack:MovieClip; + private var _scrubOverlay:MovieClip; + private var _scrubLoaded:MovieClip; + private var _hoverTime:MovieClip; + private var _hoverTimeText:TextField; + private var _playButton:SimpleButton; + private var _pauseButton:SimpleButton; + private var _duration:TextField; + private var _currentTime:TextField; + private var _fullscreenIcon:SimpleButton; + private var _volumeMuted:SimpleButton; + private var _volumeUnMuted:SimpleButton; + private var _scrubTrackColor:String; + private var _scrubBarColor:String; + private var _scrubLoadedColor:String; + + // IDLE Timer for mouse for showing/hiding controls + private var _inactiveTime:int; + private var _timer:Timer; + private var _idleTime:int; + private var _isMouseActive:Boolean + private var _isOverStage:Boolean = false; + + // security checkes + private var securityIssue:Boolean = false; // When SWF parameters contain illegal characters + private var directAccess:Boolean = false; // When SWF visited directly with no parameters (or when security issue detected) + + + public function FlashMediaElement() { + // check for security issues (borrowed from jPLayer) + checkFlashVars(loaderInfo.parameters); + + // allows this player to be called from a different domain than the HTML page hosting the player + //Security.allowDomain("*"); + //Security.allowInsecureDomain('*'); + + + // add debug output + _output = new TextField(); + _output.textColor = 0xeeeeee; + _output.width = stage.stageWidth - 100; + _output.height = stage.stageHeight; + _output.multiline = true; + _output.wordWrap = true; + _output.border = false; + _output.filters = [new DropShadowFilter(1, 0x000000, 45, 1, 2, 2, 1)]; + + _output.text = "Initializing...\n"; + addChild(_output); + _output.visible = securityIssue; + + if (securityIssue) { + _output.text = "WARNING: Security issue detected. Player stopped."; + return; + } + + // get parameters + // Use only FlashVars, ignore QueryString + var params:Object, pos:int, query:Object; + + params = LoaderInfo(this.root.loaderInfo).parameters; + pos = root.loaderInfo.url.indexOf('?'); + if (pos !== -1) { + query = parseStr(root.loaderInfo.url.substr(pos + 1)); + + for (var key:String in params) { + if (query.hasOwnProperty(trim(key))) { + delete params[key]; + } + } + } + + _mediaUrl = (params['file'] != undefined) ? String(params['file']) : ""; + _autoplay = (params['autoplay'] != undefined) ? (String(params['autoplay']) == "true") : false; + _debug = (params['debug'] != undefined) ? (String(params['debug']) == "true") : false; + _isVideo = (params['isvideo'] != undefined) ? ((String(params['isvideo']) == "false") ? false : true ) : true; + _timerRate = (params['timerrate'] != undefined) ? (parseInt(params['timerrate'], 10)) : 250; + _alwaysShowControls = (params['controls'] != undefined) ? (String(params['controls']) == "true") : false; + _enableSmoothing = (params['smoothing'] != undefined) ? (String(params['smoothing']) == "true") : false; + _startVolume = (params['startvolume'] != undefined) ? (parseFloat(params['startvolume'])) : 0.8; + _preload = (params['preload'] != undefined) ? params['preload'] : "none"; + _controlStyle = (params['controlstyle'] != undefined) ? (String(params['controlstyle'])) : ""; // blank or "floating" + _autoHide = (params['autohide'] != undefined) ? (String(params['autohide']) == "true") : true; + _scrubTrackColor = (params['scrubtrackcolor'] != undefined) ? (String(params['scrubtrackcolor'])) : "0x333333"; + _scrubBarColor = (params['scrubbarcolor'] != undefined) ? (String(params['scrubbarcolor'])) : "0xefefef"; + _scrubLoadedColor = (params['scrubloadedcolor'] != undefined) ? (String(params['scrubloadedcolor'])) : "0x3CACC8"; + _enablePseudoStreaming = (params['pseudostreaming'] != undefined) ? (String(params['pseudostreaming']) == "true") : false; + _pseudoStreamingStartQueryParam = (params['pseudostreamstart'] != undefined) ? (String(params['pseudostreamstart'])) : "start"; + _streamer = (params['flashstreamer'] != undefined) ? (String(params['flashstreamer'])) : ""; + + // for audio them controls always show them + + if (!_isVideo && _alwaysShowControls) { + _autoHide = false; + } + + _output.visible = _debug; + + if (isNaN(_timerRate)) + _timerRate = 250; + + // setup stage and player sizes/scales + stage.align = StageAlign.TOP_LEFT; + stage.scaleMode = StageScaleMode.NO_SCALE; + _stageWidth = stage.stageWidth; + _stageHeight = stage.stageHeight; + this.addChild(_mediaElementDisplay); + stage.addChild(this); + + //_autoplay = true; + //_mediaUrl = "http://mediafiles.dts.edu/chapel/mp4/20100609.mp4"; + //_alwaysShowControls = true; + //_mediaUrl = "../media/Parades-PastLives.mp3"; + //_mediaUrl = "../media/echo-hereweare.mp4"; + + //_mediaUrl = "http://video.ted.com/talks/podcast/AlGore_2006_480.mp4"; + //_mediaUrl = "rtmp://stream2.france24.yacast.net/france24_live/en/f24_liveen"; + + //_mediaUrl = "http://www.youtube.com/watch?feature=player_embedded&v=yyWWXSwtPP0"; // hosea + //_mediaUrl = "http://www.youtube.com/watch?feature=player_embedded&v=m5VDDJlsD6I"; // railer with notes + + //_alwaysShowControls = true; + + //_debug=true; + + + + + // position and hide + _fullscreenButton = _mediaElementDisplay.getChildByName("fullscreen_btn") as SimpleButton; + _fullscreenButton.visible = _isVideo; + _fullscreenButton.alpha = 0; + _fullscreenButton.addEventListener(MouseEvent.CLICK, fullscreenClick, false); + _fullscreenButton.x = stage.stageWidth - _fullscreenButton.width; + _fullscreenButton.y = stage.stageHeight - _fullscreenButton.height; + + + // create media element + if (_isVideo) { + if (_mediaUrl.search(/(https?|file)\:\/\/.*?\.m3u8(\?.*)?/i) !== -1) { + + _mediaElement = new HLSMediaElement(this, _autoplay, _preload, _timerRate, _startVolume); + _video = (_mediaElement as HLSMediaElement).video; + _video.width = _stageWidth; + _video.height = _stageHeight; + (_video as Video).smoothing = _enableSmoothing; + addChild(_video); + + + } else if (_mediaUrl.indexOf("youtube.com") > -1 || _mediaUrl.indexOf("youtu.be") > -1) { + + //Security.allowDomain("http://www.youtube.com"); + + _mediaElement = new YouTubeElement(this, _autoplay, _preload, _timerRate, _startVolume); + _video = (_mediaElement as YouTubeElement).player; + + // these are set and then used once the player is loaded + (_mediaElement as YouTubeElement).initWidth = _stageWidth; + (_mediaElement as YouTubeElement).initHeight = _stageHeight; + + } else { + + _mediaElement = new VideoElement(this, _autoplay, _preload, _timerRate, _startVolume, _streamer); + _video = (_mediaElement as VideoElement).video; + _video.width = _stageWidth; + _video.height = _stageHeight; + (_video as Video).smoothing = _enableSmoothing; + (_mediaElement as VideoElement).setReference(this); + (_mediaElement as VideoElement).setPseudoStreaming(_enablePseudoStreaming); + (_mediaElement as VideoElement).setPseudoStreamingStartParam(_pseudoStreamingStartQueryParam); + //_video.scaleMode = VideoScaleMode.MAINTAIN_ASPECT_RATIO; + addChild(_video); + } + } else { + + //var player2:AudioDecoder = new com.automatastudios.audio.audiodecoder.AudioDecoder(); + _mediaElement = new AudioElement(this, _autoplay, _preload, _timerRate, _startVolume); + } + + + // controls! + _controlBar = _mediaElementDisplay.getChildByName("controls_mc") as MovieClip; + _controlBarBg = _controlBar.getChildByName("controls_bg_mc") as MovieClip; + _scrubTrack = _controlBar.getChildByName("scrubTrack") as MovieClip; + _scrubBar = _controlBar.getChildByName("scrubBar") as MovieClip; + _scrubOverlay = _controlBar.getChildByName("scrubOverlay") as MovieClip; + _scrubLoaded = _controlBar.getChildByName("scrubLoaded") as MovieClip; + + _scrubOverlay.buttonMode = true; + _scrubOverlay.useHandCursor = true + + applyColor(_scrubTrack, _scrubTrackColor); + applyColor(_scrubBar, _scrubBarColor); + applyColor(_scrubLoaded, _scrubLoadedColor); + + _fullscreenIcon = _controlBar.getChildByName("fullscreenIcon") as SimpleButton; + _fullscreenIcon.visible = _isVideo; + + // New fullscreenIcon for new fullscreen floating controls + //if(_alwaysShowControls && _controlStyle.toUpperCase()=="FLOATING") { + _fullscreenIcon.addEventListener(MouseEvent.CLICK, fullScreenIconClick, false); + //} + + _volumeMuted = _controlBar.getChildByName("muted_mc") as SimpleButton; + _volumeUnMuted = _controlBar.getChildByName("unmuted_mc") as SimpleButton; + + _volumeMuted.addEventListener(MouseEvent.CLICK, toggleVolume, false); + _volumeUnMuted.addEventListener(MouseEvent.CLICK, toggleVolume, false); + + _playButton = _controlBar.getChildByName("play_btn") as SimpleButton; + _playButton.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void { + _mediaElement.play(); + }); + _pauseButton = _controlBar.getChildByName("pause_btn") as SimpleButton; + _pauseButton.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void { + _mediaElement.pause(); + }); + _pauseButton.visible = false; + _duration = _controlBar.getChildByName("duration_txt") as TextField; + _currentTime = _controlBar.getChildByName("currentTime_txt") as TextField; + _hoverTime = _controlBar.getChildByName("hoverTime") as MovieClip; + _hoverTimeText = _hoverTime.getChildByName("hoverTime_txt") as TextField; + _hoverTime.visible=false; + _hoverTime.y=(_hoverTime.height/2)+1; + _hoverTime.x=0; + + + + // Add new timeline scrubber events + _scrubOverlay.addEventListener(MouseEvent.MOUSE_MOVE, scrubMove); + _scrubOverlay.addEventListener(MouseEvent.CLICK, scrubClick); + _scrubOverlay.addEventListener(MouseEvent.MOUSE_OVER, scrubOver); + _scrubOverlay.addEventListener(MouseEvent.MOUSE_OUT, scrubOut); + + if (_autoHide) { // && _alwaysShowControls) { + // Add mouse activity for show/hide of controls + stage.addEventListener(Event.MOUSE_LEAVE, mouseActivityLeave); + stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseActivityMove); + _inactiveTime = 2500; + _timer = new Timer(_inactiveTime) + _timer.addEventListener(TimerEvent.TIMER, idleTimer); + _timer.start(); + // set + } + + if(_startVolume<=0) { + trace("INITIAL VOLUME: "+_startVolume+" MUTED"); + _volumeMuted.visible=true; + _volumeUnMuted.visible=false; + } else { + trace("INITIAL VOLUME: "+_startVolume+" UNMUTED"); + _volumeMuted.visible=false; + _volumeUnMuted.visible=true; + } + + _controlBar.visible = _alwaysShowControls; + + setControlDepth(); + + _output.appendText("stage: " + stage.stageWidth + "x" + stage.stageHeight + "\n"); + _output.appendText("file: " + _mediaUrl + "\n"); + _output.appendText("autoplay: " + _autoplay.toString() + "\n"); + _output.appendText("preload: " + _preload.toString() + "\n"); + _output.appendText("isvideo: " + _isVideo.toString() + "\n"); + _output.appendText("smoothing: " + _enableSmoothing.toString() + "\n"); + _output.appendText("timerrate: " + _timerRate.toString() + "\n"); + _output.appendText("displayState: " +(stage.hasOwnProperty("displayState")).toString() + "\n"); + + // attach javascript + _output.appendText("ExternalInterface.available: " + ExternalInterface.available.toString() + "\n"); + _output.appendText("ExternalInterface.objectID: " + ((ExternalInterface.objectID != null)? ExternalInterface.objectID.toString() : "null") + "\n"); + + if (_mediaUrl != "") { + _mediaElement.setSrc(_mediaUrl); + } + + positionControls(); + + // Fire this once just to set the width on some dynamically sized scrub bar items; + _scrubBar.scaleX=0; + _scrubLoaded.scaleX=0; + + + if (ExternalInterface.available) { // && !_alwaysShowControls + + _output.appendText("Adding callbacks...\n"); + try { + if (ExternalInterface.objectID != null && ExternalInterface.objectID.toString() != "") { + + // add HTML media methods + ExternalInterface.addCallback("playMedia", playMedia); + ExternalInterface.addCallback("loadMedia", loadMedia); + ExternalInterface.addCallback("pauseMedia", pauseMedia); + ExternalInterface.addCallback("stopMedia", stopMedia); + + ExternalInterface.addCallback("setSrc", setSrc); + ExternalInterface.addCallback("setCurrentTime", setCurrentTime); + ExternalInterface.addCallback("setVolume", setVolume); + ExternalInterface.addCallback("setMuted", setMuted); + + ExternalInterface.addCallback("setFullscreen", setFullscreen); + ExternalInterface.addCallback("setVideoSize", setVideoSize); + + ExternalInterface.addCallback("positionFullscreenButton", positionFullscreenButton); + ExternalInterface.addCallback("hideFullscreenButton", hideFullscreenButton); + + // fire init method + ExternalInterface.call("mejs.MediaPluginBridge.initPlugin", ExternalInterface.objectID); + } + + _output.appendText("Success...\n"); + + } catch (error:SecurityError) { + _output.appendText("A SecurityError occurred: " + error.message + "\n"); + } catch (error:Error) { + _output.appendText("An Error occurred: " + error.message + "\n"); + } + + } + + if (_preload != "none") { + _mediaElement.load(); + + if (_autoplay) { + _mediaElement.play(); + } + } else if (_autoplay) { + _mediaElement.load(); + _mediaElement.play(); + } + + // listen for resize + stage.addEventListener(Event.RESIZE, resizeHandler); + + // send click events up to javascript + stage.addEventListener(MouseEvent.CLICK, stageClicked); + + // resize + stage.addEventListener(FullScreenEvent.FULL_SCREEN, stageFullScreenChanged); + + stage.addEventListener(KeyboardEvent.KEY_DOWN, stageKeyDown); + } + + public function setControlDepth():void { + // put these on top + addChild(_output); + addChild(_controlBar); + addChild(_fullscreenButton); + + } + + // borrowed from jPLayer + // https://github.com/happyworm/jPlayer/blob/e8ca190f7f972a6a421cb95f09e138720e40ed6d/actionscript/Jplayer.as#L228 + private function checkFlashVars(p:Object):void { + var i:Number = 0; + for (var s:String in p) { + if (isIllegalChar(p[s], s === 'file')) { + securityIssue = true; // Illegal char found + } + i++; + } + if(i === 0 || securityIssue) { + directAccess = true; + } + } + + private static function parseStr (str:String) : Object { + var hash:Object = {}, + arr1:Array, arr2:Array; + + str = unescape(str).replace(/\+/g, " "); + + arr1 = str.split('&'); + if (!arr1.length) { + return {}; + } + + for (var i:uint = 0, length:uint = arr1.length; i < length; i++) { + arr2 = arr1[i].split('='); + if (!arr2.length) { + continue; + } + hash[trim(arr2[0])] = trim(arr2[1]); + } + return hash; + } + + + private static function trim(str:String) : String { + if (!str) { + return str; + } + + return str.toString().replace(/^\s*/, '').replace(/\s*$/, ''); + } + + private function isIllegalChar(s:String, isUrl:Boolean):Boolean { + var illegals:String = "' \" ( ) { } * + \\ < >"; + if(isUrl) { + illegals = "\" { } \\ < >"; + } + if(Boolean(s)) { // Otherwise exception if parameter null. + for each (var illegal:String in illegals.split(' ')) { + if(s.indexOf(illegal) >= 0) { + return true; // Illegal char found + } + } + } + return false; + } + + + // START: Controls and events + private function mouseActivityMove(event:MouseEvent):void { + + // if mouse is in the video area + if (_autoHide && (mouseX>=0 && mouseX<=stage.stageWidth) && (mouseY>=0 && mouseY<=stage.stageHeight)) { + + // This could be move to a nice fade at some point... + _controlBar.visible = (_alwaysShowControls || _isFullScreen); + _isMouseActive = true; + _idleTime = 0; + _timer.reset(); + _timer.start() + } + } + + private function mouseActivityLeave(event:Event):void { + if (_autoHide) { + _isOverStage = false; + // This could be move to a nice fade at some point... + _controlBar.visible = false; + _isMouseActive = false; + _idleTime = 0; + _timer.reset(); + _timer.stop(); + } + } + + private function idleTimer(event:TimerEvent):void { + + if (_autoHide) { + // This could be move to a nice fade at some point... + _controlBar.visible = false; + _isMouseActive = false; + _idleTime += _inactiveTime; + _idleTime = 0; + _timer.reset(); + _timer.stop(); + } + } + + + private function scrubMove(event:MouseEvent):void { + + //if (_alwaysShowControls) { + if (_hoverTime.visible) { + var seekBarPosition:Number = ((event.localX / _scrubTrack.width) *_mediaElement.duration())*_scrubTrack.scaleX; + var hoverPos:Number = (seekBarPosition / _mediaElement.duration()) *_scrubTrack.scaleX; + + if (_isFullScreen) { + _hoverTime.x=event.target.parent.mouseX; + } else { + _hoverTime.x=mouseX; + } + _hoverTime.y = _scrubBar.y - (_hoverTime.height/2); + _hoverTimeText.text = secondsToTimeCode(seekBarPosition); + } + //} + //trace(event); + } + + private function scrubOver(event:MouseEvent):void { + _hoverTime.y = _scrubBar.y-(_hoverTime.height/2)+1; + _hoverTime.visible = true; + trace(event); + } + + private function scrubOut(event:MouseEvent):void { + _hoverTime.y = _scrubBar.y+(_hoverTime.height/2)+1; + _hoverTime.visible = false; + //_hoverTime.x=0; + //trace(event); + } + + private function scrubClick(event:MouseEvent):void { + //trace(event); + var seekBarPosition:Number = ((event.localX / _scrubTrack.width) *_mediaElement.duration())*_scrubTrack.scaleX; + + var tmp:Number = (_mediaElement.currentTime()/_mediaElement.duration())*_scrubTrack.width; + var canSeekToPosition:Boolean = _scrubLoaded.scaleX > (seekBarPosition / _mediaElement.duration()) *_scrubTrack.scaleX; + //var canSeekToPosition:Boolean = true; + + /* + amountLoaded = ns.bytesLoaded / ns.bytesTotal; + loader.loadbar._width = amountLoaded * 208.9; + loader.scrub._x = ns.time / duration * 208.9; + */ + + trace("seekBarPosition:"+seekBarPosition, "CanSeekToPosition: "+canSeekToPosition); + + if (seekBarPosition>0 && seekBarPosition<_mediaElement.duration() && canSeekToPosition) { + _mediaElement.setCurrentTime(seekBarPosition); + } + } + + public function toggleVolume(event:MouseEvent):void { + trace(event.currentTarget.name); + switch(event.currentTarget.name) { + case "muted_mc": + setMuted(false); + break; + case "unmuted_mc": + setMuted(true); + break; + } + } + + private function toggleVolumeIcons(volume:Number):void { + if(volume<=0) { + _volumeMuted.visible = true; + _volumeUnMuted.visible = false; + } else { + _volumeMuted.visible = false; + _volumeUnMuted.visible = true; + } + } + + private function positionControls(forced:Boolean=false):void { + + + if ( _controlStyle.toUpperCase() == "FLOATING" && _isFullScreen) { + + trace("CONTROLS: floating"); + _hoverTime.y=(_hoverTime.height/2)+1; + _hoverTime.x=0; + _controlBarBg.width = 300; + _controlBarBg.height = 93; + //_controlBarBg.x = (stage.stageWidth/2) - (_controlBarBg.width/2); + //_controlBarBg.y = stage.stageHeight - 300; + + _pauseButton.scaleX = _playButton.scaleX=3.5; + _pauseButton.scaleY= _playButton.scaleY=3.5; + // center the play button and make it big and at the top + _pauseButton.x = _playButton.x = (_controlBarBg.width/2)-(_playButton.width/2)+7; + _pauseButton.y = _playButton.y = _controlBarBg.height-_playButton.height-(14) + + _controlBar.x = (stage.stageWidth/2) -150; + _controlBar.y = stage.stageHeight - _controlBar.height-100; + + + // reposition the time and duration items + + _duration.x = _controlBarBg.width - _duration.width - 10; + _duration.y = _controlBarBg.height - _duration.height -7; + //_currentTime.x = _controlBarBg.width - _duration.width - 10 - _currentTime.width - 10; + _currentTime.x = 5 + _currentTime.y= _controlBarBg.height - _currentTime.height-7; + + _fullscreenIcon.x = _controlBarBg.width - _fullscreenIcon.width - 7; + _fullscreenIcon.y = 7; + + _volumeMuted.x = _volumeUnMuted.x = 7; + _volumeMuted.y = _volumeUnMuted.y = 7; + + _scrubLoaded.x = _scrubBar.x = _scrubOverlay.x = _scrubTrack.x =_currentTime.x+_currentTime.width+7; + _scrubLoaded.y = _scrubBar.y = _scrubOverlay.y = _scrubTrack.y=_controlBarBg.height-_scrubTrack.height-10; + + _scrubBar.width = _scrubOverlay.width = _scrubTrack.width = (_duration.x-_duration.width-14); + + + } else { + trace("CONTROLS: normal, original"); + + /* + // Original style bottom display + _hoverTime.y=(_hoverTime.height/2)+1; + _hoverTime.x=0; + _controlBarBg.width = stage.stageWidth; + _controlBar.y = stage.stageHeight - _controlBar.height; + _duration.x = stage.stageWidth - _duration.width - 10; + //_currentTime.x = stage.stageWidth - _duration.width - 10 - _currentTime.width - 10; + _currentTime.x = _playButton.x+_playButton.width; + _scrubTrack.width = (_duration.x-_duration.width-10)-_duration.width+10; + _scrubOverlay.width = _scrubTrack.width; + _scrubBar.width = _scrubTrack.width; + */ + + // FLOATING MODE BOTTOM DISPLAY - similar to normal + trace("THAT WAY!"); + _hoverTime.y=(_hoverTime.height/2)+1; + _hoverTime.x=0; + _controlBarBg.width = stage.stageWidth; + _controlBarBg.height = 30; + _controlBarBg.y=0; + _controlBarBg.x=0; + // _controlBarBg.x = 0; + // _controlBarBg.y = stage.stageHeight - _controlBar.height; + + _pauseButton.scaleX = _playButton.scaleX=1; + _pauseButton.scaleY = _playButton.scaleY=1; + + _pauseButton.x = _playButton.x = 7; + _pauseButton.y = _playButton.y = _controlBarBg.height-_playButton.height-2; + + + //_currentTime.x = stage.stageWidth - _duration.width - 10 - _currentTime.width - 10; + _currentTime.x = _playButton.x+_playButton.width; + + _fullscreenIcon.x = _controlBarBg.width - _fullscreenIcon.width - 7; + _fullscreenIcon.y = 8; + + _volumeMuted.x = _volumeUnMuted.x = (_isVideo ? _fullscreenIcon.x : _controlBarBg.width) - _volumeMuted.width - 10; + _volumeMuted.y = _volumeUnMuted.y = 10; + + _duration.x = _volumeMuted.x - _volumeMuted.width - _duration.width + 5; + _duration.y = _currentTime.y = _controlBarBg.height - _currentTime.height - 7; + + _scrubLoaded.x = _scrubBar.x = _scrubOverlay.x = _scrubTrack.x = _currentTime.x + _currentTime.width + 10; + _scrubLoaded.y = _scrubBar.y = _scrubOverlay.y = _scrubTrack.y = _controlBarBg.height - _scrubTrack.height - 9; + + _scrubBar.width = _scrubOverlay.width = _scrubTrack.width = (_duration.x-_duration.width-10)-_duration.width+5; + _controlBar.x = 0; + _controlBar.y = stage.stageHeight - _controlBar.height; + + } + + } + + // END: Controls + + + public function stageClicked(e:MouseEvent):void { + //_output.appendText("click: " + e.stageX.toString() +","+e.stageY.toString() + "\n"); + if (e.target == stage) { + sendEvent("click", ""); + } + } + + public function stageKeyDown(e:KeyboardEvent):void { + sendEvent(HtmlMediaEvent.KEYDOWN, "keyCode:'" + e.keyCode + "'"); + } + + public function resizeHandler(e:Event):void { + + //_video.scaleX = stage.stageWidth / _stageWidth; + //_video.scaleY = stage.stageHeight / _stageHeight; + //positionControls(); + + repositionVideo(); + } + + // START: Fullscreen + private function enterFullscreen():void { + + _output.appendText("enterFullscreen()\n"); + + var screenRectangle:Rectangle = new Rectangle(0, 0, flash.system.Capabilities.screenResolutionX, flash.system.Capabilities.screenResolutionY); + stage.fullScreenSourceRect = screenRectangle; + + stage.displayState = StageDisplayState.FULL_SCREEN; + + repositionVideo(); + positionControls(); + updateControls(HtmlMediaEvent.FULLSCREENCHANGE); + + _controlBar.visible = true; + + _isFullScreen = true; + } + + private function exitFullscreen():void { + + stage.displayState = StageDisplayState.NORMAL; + + + _controlBar.visible = false; + + _isFullScreen = false; + } + + public function setFullscreen(gofullscreen:Boolean):void { + + _output.appendText("setFullscreen: " + gofullscreen.toString() + "\n"); + + try { + //_fullscreenButton.visible = false; + + if (gofullscreen) { + enterFullscreen(); + + } else { + exitFullscreen(); + } + + } catch (error:Error) { + + // show the button when the security error doesn't let it work + //_fullscreenButton.visible = true; + _fullscreenButton.alpha = 1; + + _isFullScreen = false; + + _output.appendText("error setting fullscreen: " + error.message.toString() + "\n"); + } + } + + // control bar button/icon + public function fullScreenIconClick(e:MouseEvent):void { + try { + _controlBar.visible = true; + setFullscreen(!_isFullScreen); + repositionVideo(); + } catch (error:Error) { + } + } + + // special floating fullscreen icon + public function fullscreenClick(e:MouseEvent):void { + //_fullscreenButton.visible = false; + _fullscreenButton.alpha = 0 + + try { + _controlBar.visible = true; + setFullscreen(true); + repositionVideo(); + positionControls(); + } catch (error:Error) { + } + } + + + public function stageFullScreenChanged(e:FullScreenEvent):void { + _output.appendText("fullscreen event: " + e.fullScreen.toString() + "\n"); + + //_fullscreenButton.visible = false; + _fullscreenButton.alpha = 0; + _isFullScreen = e.fullScreen; + + sendEvent(HtmlMediaEvent.FULLSCREENCHANGE, "isFullScreen:" + e.fullScreen ); + + if (!e.fullScreen) { + _controlBar.visible = _alwaysShowControls; + } + } + // END: Fullscreen + + // START: external interface + public function playMedia():void { + _output.appendText("play\n"); + _mediaElement.play(); + } + + public function loadMedia():void { + _output.appendText("load\n"); + _mediaElement.load(); + } + + public function pauseMedia():void { + _output.appendText("pause\n"); + _mediaElement.pause(); + } + + public function setSrc(url:String):void { + _output.appendText("setSrc: " + url + "\n"); + _mediaElement.setSrc(url); + } + + public function stopMedia():void { + _output.appendText("stop\n"); + _mediaElement.stop(); + } + + public function setCurrentTime(time:Number):void { + _output.appendText("seek: " + time.toString() + "\n"); + _mediaElement.setCurrentTime(time); + } + + public function setVolume(volume:Number):void { + _output.appendText("volume: " + volume.toString() + "\n"); + _mediaElement.setVolume(volume); + toggleVolumeIcons(volume); + } + + public function setMuted(muted:Boolean):void { + _output.appendText("muted: " + muted.toString() + "\n"); + _mediaElement.setMuted(muted); + toggleVolumeIcons(_mediaElement.getVolume()); + } + + public function setVideoSize(width:Number, height:Number):void { + _output.appendText("setVideoSize: " + width.toString() + "," + height.toString() + "\n"); + + _stageWidth = width; + _stageHeight = height; + + if (_video != null) { + repositionVideo(); + positionControls(); + //_fullscreenButton.x = stage.stageWidth - _fullscreenButton.width - 10; + _output.appendText("result: " + _video.width.toString() + "," + _video.height.toString() + "\n"); + } + + + } + + public function positionFullscreenButton(x:Number, y:Number, visibleAndAbove:Boolean ):void { + + _output.appendText("position FS: " + x.toString() + "x" + y.toString() + "\n"); + + // bottom corner + /* + _fullscreenButton.x = stage.stageWidth - _fullscreenButton.width + _fullscreenButton.y = stage.stageHeight - _fullscreenButton.height; + */ + + // position just above + if (visibleAndAbove) { + _fullscreenButton.x = x+1; + _fullscreenButton.y = y - _fullscreenButton.height+1; + } else { + _fullscreenButton.x = x; + _fullscreenButton.y = y; + } + + // check for oversizing + if ((_fullscreenButton.x + _fullscreenButton.width) > stage.stageWidth) + _fullscreenButton.x = stage.stageWidth - _fullscreenButton.width; + + // show it! + if (visibleAndAbove) { + _fullscreenButton.alpha = 1; + } + } + + public function hideFullscreenButton():void { + + //_fullscreenButton.visible = false; + _fullscreenButton.alpha = 0; + } + + // END: external interface + + + private function repositionVideo():void { + + if (stage.displayState == "fullScreen") { + fullscreen = true; + } else { + fullscreen = false; + } + + _output.appendText("positioning video "+stage.displayState+"\n"); + + if (_mediaElement is VideoElement || _mediaElement is HLSMediaElement) { + + if (isNaN(_nativeVideoWidth) || isNaN(_nativeVideoHeight) || _nativeVideoWidth <= 0 || _nativeVideoHeight <= 0) { + _output.appendText("ERR: I dont' have the native dimension\n"); + return; + } + + // calculate ratios + var stageRatio:Number, nativeRatio:Number; + + _video.x = 0; + _video.y = 0; + + if(fullscreen == true) { + stageRatio = flash.system.Capabilities.screenResolutionX/flash.system.Capabilities.screenResolutionY; + nativeRatio = _nativeVideoWidth/_nativeVideoHeight; + + // adjust size and position + if (nativeRatio > stageRatio) { + _mediaElement.setSize(flash.system.Capabilities.screenResolutionX, _nativeVideoHeight * flash.system.Capabilities.screenResolutionX / _nativeVideoWidth); + _video.y = flash.system.Capabilities.screenResolutionY/2 - _video.height/2; + } else if (stageRatio > nativeRatio) { + _mediaElement.setSize(_nativeVideoWidth * flash.system.Capabilities.screenResolutionY / _nativeVideoHeight, flash.system.Capabilities.screenResolutionY); + _video.x = flash.system.Capabilities.screenResolutionX/2 - _video.width/2; + } else if (stageRatio == nativeRatio) { + _mediaElement.setSize(flash.system.Capabilities.screenResolutionX, flash.system.Capabilities.screenResolutionY); + } + + } else { + stageRatio = _stageWidth/_stageHeight; + nativeRatio = _nativeVideoWidth/_nativeVideoHeight; + + // adjust size and position + if (nativeRatio > stageRatio) { + _mediaElement.setSize(_stageWidth, _nativeVideoHeight * _stageWidth / _nativeVideoWidth); + _video.y = _stageHeight/2 - _video.height/2; + } else if (stageRatio > nativeRatio) { + _mediaElement.setSize( _nativeVideoWidth * _stageHeight / _nativeVideoHeight, _stageHeight); + _video.x = _stageWidth/2 - _video.width/2; + } else if (stageRatio == nativeRatio) { + _mediaElement.setSize(_stageWidth, _stageHeight); + } + + } + + } else if (_mediaElement is YouTubeElement) { + if(fullscreen == true) { + _mediaElement.setSize(flash.system.Capabilities.screenResolutionX, flash.system.Capabilities.screenResolutionY); + + } else { + _mediaElement.setSize(_stageWidth, _stageHeight); + + } + + } + + positionControls(); + } + + // SEND events to JavaScript + public function sendEvent(eventName:String, eventValues:String):void { + + // special video event + if (eventName == HtmlMediaEvent.LOADEDMETADATA && _isVideo) { + + _output.appendText("METADATA RECEIVED: "); + + try { + if (_mediaElement is VideoElement) { + _nativeVideoWidth = (_mediaElement as VideoElement).videoWidth; + _nativeVideoHeight = (_mediaElement as VideoElement).videoHeight; + } else if(_mediaElement is HLSMediaElement) { + _nativeVideoWidth = (_mediaElement as HLSMediaElement).videoWidth; + _nativeVideoHeight = (_mediaElement as HLSMediaElement).videoHeight; + } + } catch (e:Error) { + _output.appendText(e.toString() + "\n"); + } + + _output.appendText(_nativeVideoWidth.toString() + "x" + _nativeVideoHeight.toString() + "\n"); + + + if(stage.displayState == "fullScreen" ) { + setVideoSize(_nativeVideoWidth, _nativeVideoHeight); + } + repositionVideo(); + + } + + updateControls(eventName); + + //trace((_mediaElement.duration()*1).toString() + " / " + (_mediaElement.currentTime()*1).toString()); + //trace("CurrentProgress:"+_mediaElement.currentProgress()); + + if (ExternalInterface.objectID != null && ExternalInterface.objectID.toString() != "") { + + //_output.appendText("event:" + eventName + " : " + eventValues); + //trace("event", eventName, eventValues); + + if (eventValues == null) + eventValues == ""; + + if (_isVideo) { + eventValues += (eventValues != "" ? "," : "") + "isFullScreen:" + _isFullScreen; + } + + eventValues = "{" + eventValues + "}"; + + /* + OLD DIRECT METHOD + ExternalInterface.call( + "function(id, name) { mejs.MediaPluginBridge.fireEvent(id,name," + eventValues + "); }", + ExternalInterface.objectID, + eventName); + */ + + // use set timeout for performance reasons + //if (!_alwaysShowControls) { + ExternalInterface.call("setTimeout", "mejs.MediaPluginBridge.fireEvent('" + ExternalInterface.objectID + "','" + eventName + "'," + eventValues + ")",0); + //} + } + } + + + private function updateControls(eventName:String):void { + + //trace("updating controls"); + + try { + // update controls + switch (eventName) { + case "pause": + case "paused": + case "ended": + _playButton.visible = true; + _pauseButton.visible = false; + break; + case "play": + case "playing": + _playButton.visible = false; + _pauseButton.visible = true; + break; + } + + if (eventName == HtmlMediaEvent.TIMEUPDATE || + eventName == HtmlMediaEvent.PROGRESS || + eventName == HtmlMediaEvent.FULLSCREENCHANGE) { + + //_duration.text = (_mediaElement.duration()*1).toString(); + _duration.text = secondsToTimeCode(_mediaElement.duration()); + //_currentTime.text = (_mediaElement.currentTime()*1).toString(); + _currentTime.text = secondsToTimeCode(_mediaElement.currentTime()); + + var pct:Number = (_mediaElement.currentTime() / _mediaElement.duration()) *_scrubTrack.scaleX; + + _scrubBar.scaleX = pct; + _scrubLoaded.scaleX = (_mediaElement.currentProgress()*_scrubTrack.scaleX)/100; + } + } catch (error:Error) { + trace("error: " + error.toString()); + + } + + } + + // START: utility + private function secondsToTimeCode(seconds:Number):String { + var timeCode:String = ""; + seconds = Math.round(seconds); + var minutes:Number = Math.floor(seconds / 60); + timeCode = (minutes >= 10) ? minutes.toString() : "0" + minutes.toString(); + seconds = Math.floor(seconds % 60); + timeCode += ":" + ((seconds >= 10) ? seconds.toString() : "0" + seconds.toString()); + return timeCode; //minutes.toString() + ":" + seconds.toString(); + } + + private function applyColor(item:Object, color:String):void { + + var myColor:ColorTransform = item.transform.colorTransform; + myColor.color = Number(color); + item.transform.colorTransform = myColor; + } + // END: utility + + } +} diff --git a/js/mediaelement/src/flash/FlashMediaElement.fla b/js/mediaelement/src/flash/FlashMediaElement.fla new file mode 100644 index 0000000000000000000000000000000000000000..5be28e542f9c072e8ccf575a6f89a49d1789a518 Binary files /dev/null and b/js/mediaelement/src/flash/FlashMediaElement.fla differ diff --git a/js/mediaelement/src/flash/HtmlMediaEvent.as b/js/mediaelement/src/flash/HtmlMediaEvent.as new file mode 100644 index 0000000000000000000000000000000000000000..f94e1bc5295a2f4ed0bed23058ec9e2882a6abaf --- /dev/null +++ b/js/mediaelement/src/flash/HtmlMediaEvent.as @@ -0,0 +1,32 @@ +package { + + public class HtmlMediaEvent { + + public static var LOADED_DATA:String = "loadeddata"; + public static var PROGRESS:String = "progress"; + public static var TIMEUPDATE:String = "timeupdate"; + public static var SEEKED:String = "seeked"; + public static var PLAY:String = "play"; + public static var PLAYING:String = "playing"; + public static var PAUSE:String = "pause"; + public static var LOADEDMETADATA:String = "loadedmetadata"; + public static var ENDED:String = "ended"; + public static var VOLUMECHANGE:String = "volumechange"; + public static var STOP:String = "stop"; + + // new : 2/15/2011 + public static var LOADSTART:String = "loadstart"; + public static var CANPLAY:String = "canplay"; + // new : 3/3/2011 + public static var LOADEDDATA:String = "loadeddata"; + + // new : 4/12/2011 + public static var SEEKING:String = "seeking"; + + // new : 1/2/2012 + public static var FULLSCREENCHANGE:String = "fullscreenchange"; + + // new : 2/18/2014 + public static var KEYDOWN:String = "keydown"; + } +} diff --git a/js/mediaelement/src/flash/flashls.swc b/js/mediaelement/src/flash/flashls.swc new file mode 100755 index 0000000000000000000000000000000000000000..3cd2055b8c5a976883127ad78cdc56c4313b0ba0 Binary files /dev/null and b/js/mediaelement/src/flash/flashls.swc differ diff --git a/js/mediaelement/src/flash/flashmediaelement.swc b/js/mediaelement/src/flash/flashmediaelement.swc new file mode 100644 index 0000000000000000000000000000000000000000..fda499ab5719bf0279c10c17b323eb30d7449e5d Binary files /dev/null and b/js/mediaelement/src/flash/flashmediaelement.swc differ diff --git a/js/mediaelement/src/flash/htmlelements/AudioElement.as b/js/mediaelement/src/flash/htmlelements/AudioElement.as new file mode 100644 index 0000000000000000000000000000000000000000..b35f7cc26e128c34b4cb359b47f2c0940bfe7a39 --- /dev/null +++ b/js/mediaelement/src/flash/htmlelements/AudioElement.as @@ -0,0 +1,343 @@ + +package htmlelements +{ + import flash.events.Event; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.TimerEvent; + import flash.media.ID3Info; + import flash.media.Sound; + import flash.media.SoundChannel; + import flash.media.SoundLoaderContext; + import flash.media.SoundTransform; + import flash.net.URLRequest; + import flash.utils.Timer; + + + + /** + * ... + * @author DefaultUser (Tools -> Custom Arguments...) + */ + public class AudioElement implements IMediaElement + { + + private var _sound:Sound; + private var _soundTransform:SoundTransform; + private var _soundChannel:SoundChannel; + private var _soundLoaderContext:SoundLoaderContext; + + private var _volume:Number = 1; + private var _preMuteVolume:Number = 0; + private var _isMuted:Boolean = false; + private var _isPaused:Boolean = true; + private var _isEnded:Boolean = false; + private var _isLoaded:Boolean = false; + private var _currentTime:Number = 0; + private var _duration:Number = 0; + private var _bytesLoaded:Number = 0; + private var _bytesTotal:Number = 0; + private var _bufferedTime:Number = 0; + private var _bufferingChanged:Boolean = false; + + private var _currentUrl:String = ""; + private var _autoplay:Boolean = true; + private var _preload:String = ""; + + private var _element:FlashMediaElement; + private var _timer:Timer; + private var _firedCanPlay:Boolean = false; + + public function setSize(width:Number, height:Number):void { + // do nothing! + } + + public function duration():Number { + return _duration; + } + + public function currentTime():Number { + return _currentTime; + } + + public function currentProgress():Number { + return Math.round(_bytesLoaded/_bytesTotal*100); + } + + public function AudioElement(element:FlashMediaElement, autoplay:Boolean, preload:String, timerRate:Number, startVolume:Number):void + { + _element = element; + _autoplay = autoplay; + _volume = startVolume; + _preload = preload; + + _timer = new Timer(timerRate); + _timer.addEventListener(TimerEvent.TIMER, timerEventHandler); + + _soundTransform = new SoundTransform(_volume); + _soundLoaderContext = new SoundLoaderContext(); + } + + // events + private function progressHandler(e:ProgressEvent):void { + + _bytesLoaded = e.bytesLoaded; + _bytesTotal = e.bytesTotal; + + // this happens too much to send every time + //sendEvent(HtmlMediaEvent.PROGRESS); + + // so now we just trigger a flag and send with the timer + _bufferingChanged = true; + } + + private function id3Handler(e:Event):void { + sendEvent(HtmlMediaEvent.LOADEDMETADATA); + + try { + var id3:ID3Info = _sound.id3; + var obj:Object = { + type:'id3', + album:id3.album, + artist:id3.artist, + comment:id3.comment, + genre:id3.genre, + songName:id3.songName, + track:id3.track, + year:id3.year + }; + } catch (err:Error) {} + + + } + + private function timerEventHandler(e:TimerEvent):void { + _currentTime = _soundChannel.position/1000; + + // calculate duration + var duration:Number = Math.round(_sound.length * _sound.bytesTotal/_sound.bytesLoaded/100) / 10; + + // check to see if the estimated duration changed + if (_duration != duration && !isNaN(duration)) { + + _duration = duration; + sendEvent(HtmlMediaEvent.LOADEDMETADATA); + } + + // check for progress + if (_bufferingChanged) { + + sendEvent(HtmlMediaEvent.PROGRESS); + + _bufferingChanged = false; + } + + // send timeupdate + sendEvent(HtmlMediaEvent.TIMEUPDATE); + + // sometimes the ended event doesn't fire, here's a fake one + if (_duration > 0 && _currentTime >= _duration-0.2) { + handleEnded(); + } + } + + private function soundCompleteHandler(e:Event):void { + handleEnded(); + } + + private function handleEnded():void { + _timer.stop(); + _currentTime = 0; + _isEnded = true; + + sendEvent(HtmlMediaEvent.ENDED); + } + + //events + + + // METHODS + public function setSrc(url:String):void { + _currentUrl = url; + _isLoaded = false; + } + + + public function load():void { + + if (_currentUrl == "") { + return; + } + + if (_sound) { + if (_sound.hasEventListener(ProgressEvent.PROGRESS)) { + _sound.removeEventListener(ProgressEvent.PROGRESS, progressHandler); + } + + if (_sound.hasEventListener(Event.ID3)) { + _sound.removeEventListener(Event.ID3, id3Handler); + } + } + + _sound = new Sound(); + //sound.addEventListener(IOErrorEvent.IO_ERROR,errorHandler); + _sound.addEventListener(ProgressEvent.PROGRESS,progressHandler); + _sound.addEventListener(Event.ID3,id3Handler); + _sound.load(new URLRequest(_currentUrl)); + _currentTime = 0; + + sendEvent(HtmlMediaEvent.LOADSTART); + + _isLoaded = true; + + sendEvent(HtmlMediaEvent.LOADEDDATA); + sendEvent(HtmlMediaEvent.CANPLAY); + _firedCanPlay = true; + + if (_playAfterLoading) { + _playAfterLoading = false; + play(); + } + } + + private var _playAfterLoading:Boolean= false; + + public function play():void { + + if (!_isLoaded) { + _playAfterLoading = true; + load(); + return; + } + + _timer.stop(); + + _soundChannel = _sound.play(_currentTime*1000, 0, _soundTransform); + _soundChannel.removeEventListener(Event.SOUND_COMPLETE, soundCompleteHandler); + _soundChannel.addEventListener(Event.SOUND_COMPLETE, soundCompleteHandler); + + _timer.start(); + + didStartPlaying(); + } + + public function pause():void { + + _timer.stop(); + if (_soundChannel != null) { + _currentTime = _soundChannel.position/1000; + _soundChannel.stop(); + } + + _isPaused = true; + sendEvent(HtmlMediaEvent.PAUSE); + } + + + public function stop():void { + if (_timer != null) { + _timer.stop(); + } + if (_soundChannel != null) { + _soundChannel.stop(); + _sound.close(); + } + unload(); + sendEvent(HtmlMediaEvent.STOP); + } + + public function setCurrentTime(pos:Number):void { + sendEvent(HtmlMediaEvent.SEEKING); + _timer.stop(); + _currentTime = pos; + _soundChannel.stop(); + _sound.length + _soundChannel = _sound.play(_currentTime * 1000, 0, _soundTransform); + sendEvent(HtmlMediaEvent.SEEKED); + + _timer.start(); + + didStartPlaying(); + } + + private function didStartPlaying():void { + _isPaused = false; + sendEvent(HtmlMediaEvent.PLAY); + sendEvent(HtmlMediaEvent.PLAYING); + if (!_firedCanPlay) { + sendEvent(HtmlMediaEvent.LOADEDDATA); + sendEvent(HtmlMediaEvent.CANPLAY); + _firedCanPlay = true; + } + } + + + public function setVolume(volume:Number):void { + + _volume = volume; + _soundTransform.volume = volume; + + if (_soundChannel != null) { + _soundChannel.soundTransform = _soundTransform; + } + + _isMuted = (_volume == 0); + + sendEvent(HtmlMediaEvent.VOLUMECHANGE); + } + + public function getVolume():Number { + if(_isMuted) { + return 0; + } else { + return _volume; + } + } + + + public function setMuted(muted:Boolean):void { + + // ignore if already set + if ( (muted && _isMuted) || (!muted && !_isMuted)) + return; + + if (muted) { + _preMuteVolume = _soundTransform.volume; + setVolume(0); + } else { + setVolume(_preMuteVolume); + } + + _isMuted = muted; + } + + public function unload():void { + _sound = null; + _isLoaded = false; + } + + private function sendEvent(eventName:String):void { + + // calculate this to mimic HTML5 + _bufferedTime = _bytesLoaded / _bytesTotal * _duration; + + // build JSON + var values:String = "duration:" + _duration + + ",currentTime:" + _currentTime + + ",muted:" + _isMuted + + ",paused:" + _isPaused + + ",ended:" + _isEnded + + ",volume:" + _volume + + ",src:\"" + _currentUrl + "\"" + + ",bytesTotal:" + _bytesTotal + + ",bufferedBytes:" + _bytesLoaded + + ",bufferedTime:" + _bufferedTime + + ""; + + _element.sendEvent(eventName, values); + } + + } + +} + diff --git a/js/mediaelement/src/flash/htmlelements/HLSMediaElement.as b/js/mediaelement/src/flash/htmlelements/HLSMediaElement.as new file mode 100644 index 0000000000000000000000000000000000000000..d6c20e6756b4ebfd12ba6e3dfd140f617dc8ac2d --- /dev/null +++ b/js/mediaelement/src/flash/htmlelements/HLSMediaElement.as @@ -0,0 +1,250 @@ +package htmlelements +{ + import flash.display.Sprite; + import flash.media.Video; + import flash.media.SoundTransform; + import org.mangui.hls.HLS; + import org.mangui.hls.HLSEvent; + import org.mangui.hls.HLSPlayStates; + import org.mangui.hls.utils.Log; + +public class HLSMediaElement extends Sprite implements IMediaElement { + + private var _element:FlashMediaElement; + private var _playqueued:Boolean = false; + private var _autoplay:Boolean = true; + private var _preload:String = ""; + private var _hls:HLS; + private var _url:String; + private var _video:Video; + private var _hlsState:String = HLSPlayStates.IDLE; + + // event values + private var _position:Number = 0; + private var _duration:Number = 0; + private var _framerate:Number; + private var _isManifestLoaded:Boolean = false; + private var _isPaused:Boolean = true; + private var _isEnded:Boolean = false; + private var _volume:Number = 1; + private var _isMuted:Boolean = false; + + private var _bytesLoaded:Number = 0; + private var _bytesTotal:Number = 0; + private var _bufferedTime:Number = 0; + private var _bufferEmpty:Boolean = false; + private var _bufferingChanged:Boolean = false; + private var _seekOffset:Number = 0; + + + private var _videoWidth:Number = -1; + private var _videoHeight:Number = -1; + + + public function HLSMediaElement(element:FlashMediaElement, autoplay:Boolean, preload:String, timerRate:Number, startVolume:Number) + { + _element = element; + _autoplay = autoplay; + _volume = startVolume; + _preload = preload; + _video = new Video(); + addChild(_video); + _hls = new HLS(); + _hls.addEventListener(HLSEvent.PLAYBACK_COMPLETE,_completeHandler); + _hls.addEventListener(HLSEvent.ERROR,_errorHandler); + _hls.addEventListener(HLSEvent.MANIFEST_LOADED,_manifestHandler); + _hls.addEventListener(HLSEvent.MEDIA_TIME,_mediaTimeHandler); + _hls.addEventListener(HLSEvent.PLAYBACK_STATE,_stateHandler); + _hls.stream.soundTransform = new SoundTransform(_volume); + _video.attachNetStream(_hls.stream); + } + + private function _completeHandler(event:HLSEvent):void { + _isEnded = true; + _isPaused = false; + sendEvent(HtmlMediaEvent.ENDED); + }; + + private function _errorHandler(event:HLSEvent):void { + }; + + private function _manifestHandler(event:HLSEvent):void { + _duration = event.levels[0].duration; + _videoWidth = event.levels[0].width; + _videoHeight = event.levels[0].height; + _isManifestLoaded = true; + sendEvent(HtmlMediaEvent.LOADEDMETADATA); + sendEvent(HtmlMediaEvent.CANPLAY); + if(_autoplay || _playqueued) { + _playqueued = false; + _hls.stream.play(); + } + }; + + private function _mediaTimeHandler(event:HLSEvent):void { + _position = event.mediatime.position; + _duration = event.mediatime.duration; + _bufferedTime = event.mediatime.buffer+event.mediatime.position; + sendEvent(HtmlMediaEvent.PROGRESS); + sendEvent(HtmlMediaEvent.TIMEUPDATE); + }; + + private function _stateHandler(event:HLSEvent):void { + _hlsState = event.state; + //Log.txt("state:"+ _hlsState); + switch(event.state) { + case HLSPlayStates.IDLE: + break; + case HLSPlayStates.PAUSED_BUFFERING: + case HLSPlayStates.PLAYING_BUFFERING: + break; + case HLSPlayStates.PLAYING: + _isPaused = false; + _isEnded = false; + _video.visible = true; + sendEvent(HtmlMediaEvent.LOADEDDATA); + sendEvent(HtmlMediaEvent.PLAY); + sendEvent(HtmlMediaEvent.PLAYING); + break; + case HLSPlayStates.PAUSED: + _isPaused = true; + _isEnded = false; + sendEvent(HtmlMediaEvent.PAUSE); + sendEvent(HtmlMediaEvent.CANPLAY); + break; + } + }; + + public function get video():Video { + return _video; + } + + public function get videoHeight():Number { + return _videoHeight; + } + + public function get videoWidth():Number { + return _videoWidth; + } + + public function play():void { + //Log.txt("HLSMediaElement:play"); + if(!_isManifestLoaded) { + _playqueued = true; + return; + } + if (_hlsState == HLSPlayStates.PAUSED || _hlsState == HLSPlayStates.PAUSED_BUFFERING) { + _hls.stream.resume(); + } else { + _hls.stream.play(); + } + } + + public function pause():void { + if(!_isManifestLoaded) + return; + //Log.txt("HLSMediaElement:pause"); + _hls.stream.pause(); + } + + public function load():void{ + //Log.txt("HLSMediaElement:load"); + if(_url) { + sendEvent(HtmlMediaEvent.LOADSTART); + _hls.load(_url); + } + } + + public function stop():void{ + _hls.stream.close(); + _video.clear(); + _isManifestLoaded = false; + _duration = 0; + _position = 0; + _playqueued = false; + sendEvent(HtmlMediaEvent.STOP); + } + + public function setSrc(url:String):void{ + //Log.txt("HLSMediaElement:setSrc:"+url); + stop(); + _url = url; + _hls.load(_url); + } + + public function setSize(width:Number, height:Number):void{ + _video.width = width; + _video.height = height; + } + + public function setCurrentTime(pos:Number):void{ + if(!_isManifestLoaded) + return; + sendEvent(HtmlMediaEvent.SEEKING); + _hls.stream.seek(pos); + } + + public function setVolume(vol:Number):void{ + _volume = vol; + _isMuted = (_volume == 0); + _hls.stream.soundTransform = new SoundTransform(vol); + sendEvent(HtmlMediaEvent.VOLUMECHANGE); + } + + public function getVolume():Number { + if(_isMuted) { + return 0; + } else { + return _volume; + } + } + + public function setMuted(muted:Boolean):void { + + // ignore if already set + if ( (muted && _isMuted) || (!muted && !_isMuted)) + return; + + if (muted) { + _hls.stream.soundTransform = new SoundTransform(0); + } else { + setVolume(_volume); + } + + _isMuted = muted; + } + + public function duration():Number{ + return _duration; + } + + public function currentTime():Number{ + return _position; + } + + public function currentProgress():Number{ + } + + private function sendEvent(eventName:String):void { + + // build JSON + var values:String = + "duration:" + _duration + + ",framerate:" + _hls.stream.currentFPS + + ",currentTime:" + _position + + ",muted:" + _isMuted + + ",paused:" + _isPaused + + ",ended:" + _isEnded + + ",volume:" + _volume + + ",src:\"" + _url + "\"" + + ",bytesTotal:" + Math.round(1000*_duration) + + ",bufferedBytes:" + Math.round(1000*(_position+_bufferedTime)) + + ",bufferedTime:" + _bufferedTime + + ",videoWidth:" + _videoWidth + + ",videoHeight:" + _videoHeight + + ""; + _element.sendEvent(eventName, values); + } + + } +} diff --git a/js/mediaelement/src/flash/htmlelements/IMediaElement.as b/js/mediaelement/src/flash/htmlelements/IMediaElement.as new file mode 100644 index 0000000000000000000000000000000000000000..d62660795a0bbb6ea95a2a930184df7787cff0ad --- /dev/null +++ b/js/mediaelement/src/flash/htmlelements/IMediaElement.as @@ -0,0 +1,35 @@ + +package htmlelements +{ + + public interface IMediaElement { + + function play():void; + + function pause():void; + + function load():void; + + function stop():void; + + function setSrc(url:String):void; + + function setSize(width:Number, height:Number):void; + + function setCurrentTime(pos:Number):void; + + function setVolume(vol:Number):void; + + function getVolume():Number; + + function setMuted(muted:Boolean):void; + + function duration():Number; + + function currentTime():Number; + + function currentProgress():Number; + + } + +} diff --git a/js/mediaelement/src/flash/htmlelements/VideoElement.as b/js/mediaelement/src/flash/htmlelements/VideoElement.as new file mode 100644 index 0000000000000000000000000000000000000000..ad8d116358d7c0df12096e41f0397945f2e1c806 --- /dev/null +++ b/js/mediaelement/src/flash/htmlelements/VideoElement.as @@ -0,0 +1,542 @@ +package htmlelements +{ +import flash.display.Sprite; +import flash.events.*; +import flash.net.NetConnection; +import flash.net.NetStream; +import flash.media.Video; +import flash.media.SoundTransform; +import flash.utils.Timer; + +import FlashMediaElement; +import HtmlMediaEvent; + +public class VideoElement extends Sprite implements IMediaElement +{ + private var _currentUrl:String = ""; + private var _autoplay:Boolean = true; + private var _preload:String = ""; + private var _isPreloading:Boolean = false; + + private var _connection:NetConnection; + private var _stream:NetStream; + private var _video:Video; + private var _element:FlashMediaElement; + private var _soundTransform:SoundTransform; + private var _oldVolume:Number = 1; + + // event values + private var _duration:Number = 0; + private var _framerate:Number; + private var _isPaused:Boolean = true; + private var _isEnded:Boolean = false; + private var _volume:Number = 1; + private var _isMuted:Boolean = false; + + private var _bytesLoaded:Number = 0; + private var _bytesTotal:Number = 0; + private var _bufferedTime:Number = 0; + private var _bufferEmpty:Boolean = false; + private var _bufferingChanged:Boolean = false; + private var _seekOffset:Number = 0; + + + private var _videoWidth:Number = -1; + private var _videoHeight:Number = -1; + + private var _timer:Timer; + + private var _isRTMP:Boolean = false; + private var _streamer:String = ""; + private var _isConnected:Boolean = false; + private var _playWhenConnected:Boolean = false; + private var _hasStartedPlaying:Boolean = false; + + private var _parentReference:Object; + private var _pseudoStreamingEnabled:Boolean = false; + private var _pseudoStreamingStartQueryParam:String = "start"; + + public function setReference(arg:Object):void { + _parentReference = arg; + } + + public function setSize(width:Number, height:Number):void { + _video.width = width; + _video.height = height; + } + + public function setPseudoStreaming(enablePseudoStreaming:Boolean):void { + _pseudoStreamingEnabled = enablePseudoStreaming; + } + + public function setPseudoStreamingStartParam(pseudoStreamingStartQueryParam:String):void { + _pseudoStreamingStartQueryParam = pseudoStreamingStartQueryParam; + } + + public function get video():Video { + return _video; + } + + public function get videoHeight():Number { + return _videoHeight; + } + + public function get videoWidth():Number { + return _videoWidth; + } + + + public function duration():Number { + return _duration; + } + + public function currentProgress():Number { + if(_stream != null) { + return Math.round(_stream.bytesLoaded/_stream.bytesTotal*100); + } else { + return 0; + } + } + + public function currentTime():Number { + var currentTime:Number = 0; + if (_stream != null) { + currentTime = _stream.time; + if (_pseudoStreamingEnabled) { + currentTime += _seekOffset; + } + } + return currentTime; + } + + + // (1) load() + // calls _connection.connect(); + // waits for NetConnection.Connect.Success + // _stream gets created + + + public function VideoElement(element:FlashMediaElement, autoplay:Boolean, preload:String, timerRate:Number, startVolume:Number, streamer:String) + { + _element = element; + _autoplay = autoplay; + _volume = startVolume; + _preload = preload; + _streamer = streamer; + + _video = new Video(); + addChild(_video); + + _connection = new NetConnection(); + _connection.client = { onBWDone: function():void{} }; + _connection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); + _connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); + //_connection.connect(null); + + _timer = new Timer(timerRate); + _timer.addEventListener("timer", timerHandler); + + } + + private function timerHandler(e:TimerEvent):void { + + _bytesLoaded = _stream.bytesLoaded; + _bytesTotal = _stream.bytesTotal; + + if (!_isPaused) { + sendEvent(HtmlMediaEvent.TIMEUPDATE); + } + + //trace("bytes", _bytesLoaded, _bytesTotal); + + if (_bytesLoaded < _bytesTotal) { + sendEvent(HtmlMediaEvent.PROGRESS); + } + } + + // internal events + private function netStatusHandler(event:NetStatusEvent):void { + trace("netStatus", event.info.code); + + switch (event.info.code) { + + case "NetStream.Buffer.Empty": + _bufferEmpty = true; + _isEnded ? sendEvent(HtmlMediaEvent.ENDED) : null; + break; + + case "NetStream.Buffer.Full": + _bytesLoaded = _stream.bytesLoaded; + _bytesTotal = _stream.bytesTotal; + _bufferEmpty = false; + + sendEvent(HtmlMediaEvent.PROGRESS); + break; + + case "NetConnection.Connect.Success": + connectStream(); + sendEvent(HtmlMediaEvent.LOADEDDATA); + sendEvent(HtmlMediaEvent.CANPLAY); + break; + case "NetStream.Play.StreamNotFound": + trace("Unable to locate video"); + break; + + // STREAM + case "NetStream.Play.Start": + + _isPaused = false; + sendEvent(HtmlMediaEvent.LOADEDDATA); + sendEvent(HtmlMediaEvent.CANPLAY); + + if (!_isPreloading) { + + sendEvent(HtmlMediaEvent.PLAY); + sendEvent(HtmlMediaEvent.PLAYING); + + } + + _timer.start(); + + break; + + case "NetStream.Seek.Notify": + sendEvent(HtmlMediaEvent.SEEKED); + break; + + case "NetStream.Pause.Notify": + _isPaused = true; + sendEvent(HtmlMediaEvent.PAUSE); + break; + + case "NetStream.Play.Stop": + _isEnded = true; + _isPaused = false; + _timer.stop(); + _bufferEmpty ? sendEvent(HtmlMediaEvent.ENDED) : null; + break; + + } + } + + + private function securityErrorHandler(event:SecurityErrorEvent):void { + trace("securityErrorHandler: " + event); + } + + private function asyncErrorHandler(event:AsyncErrorEvent):void { + // ignore AsyncErrorEvent events. + } + + + private function onMetaDataHandler(info:Object):void { + // Only set the duration when we first load the video + if (_duration == 0) { + _duration = info.duration; + } + _framerate = info.framerate; + _videoWidth = info.width; + _videoHeight = info.height; + + + // set size? + + sendEvent(HtmlMediaEvent.LOADEDMETADATA); + + + + if (_isPreloading) { + + _stream.pause(); + _isPaused = true; + _isPreloading = false; + + sendEvent(HtmlMediaEvent.PROGRESS); + sendEvent(HtmlMediaEvent.TIMEUPDATE); + + } + } + + + + + // interface members + public function setSrc(url:String):void { + if (_isConnected && _stream) { + // stop and restart + _stream.pause(); + } + + _duration = 0; + _currentUrl = url; + _isRTMP = !!_currentUrl.match(/^rtmp(s|t|e|te)?\:\/\//) || _streamer != ""; + _isConnected = false; + _hasStartedPlaying = false; + } + + public function load():void { + // disconnect existing stream and connection + if (_isConnected && _stream) { + _stream.pause(); + _stream.close(); + _connection.close(); + } + _isConnected = false; + _isPreloading = false; + + + _isEnded = false; + _bufferEmpty = false; + + // start new connection + if (_isRTMP) { + var rtmpInfo:Object = parseRTMP(_currentUrl); + if (_streamer != "") { + rtmpInfo.server = _streamer; + rtmpInfo.stream = _currentUrl; + + } + _connection.connect(rtmpInfo.server); + } else { + _connection.connect(null); + } + + // in a few moments the "NetConnection.Connect.Success" event will fire + // and call createConnection which finishes the "load" sequence + sendEvent(HtmlMediaEvent.LOADSTART); + } + + + private function connectStream():void { + trace("connectStream"); + _stream = new NetStream(_connection); + + // explicitly set the sound since it could have come before the connection was made + _soundTransform = new SoundTransform(_volume); + _stream.soundTransform = _soundTransform; + + // set the buffer to ensure nice playback + _stream.bufferTime = 1; + _stream.bufferTimeMax = 3; + + _stream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); // same event as connection + _stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler); + + var customClient:Object = new Object(); + customClient.onMetaData = onMetaDataHandler; + _stream.client = customClient; + + _video.attachNetStream(_stream); + + // start downloading without playing )based on preload and play() hasn't been called) + // I wish flash had a load() command to make this less awkward + if (_preload != "none" && !_playWhenConnected) { + _isPaused = true; + //stream.bufferTime = 20; + _stream.play(getCurrentUrl(0), 0, 0); + _stream.pause(); + + _isPreloading = true; + + //_stream.pause(); + // + //sendEvent(HtmlMediaEvent.PAUSE); // have to send this because the "playing" event gets sent via event handlers + } + + _isConnected = true; + + if (_playWhenConnected && !_hasStartedPlaying) { + play(); + _playWhenConnected = false; + } + + } + + public function play():void { + + if (!_hasStartedPlaying && !_isConnected) { + _playWhenConnected = true; + load(); + return; + } + + if (_hasStartedPlaying) { + if (_isPaused) { + _stream.resume(); + _timer.start(); + _isPaused = false; + sendEvent(HtmlMediaEvent.PLAY); + sendEvent(HtmlMediaEvent.PLAYING); + } + } else { + + if (_isRTMP) { + var rtmpInfo:Object = parseRTMP(_currentUrl); + _stream.play(rtmpInfo.stream); + } else { + _stream.play(getCurrentUrl(0)); + } + _timer.start(); + _isPaused = false; + _hasStartedPlaying = true; + + // don't toss play/playing events here, because we haven't sent a + // canplay / loadeddata event yet. that'll be handled in the net + // event listener + } + + } + + public function pause():void { + if (_stream == null) + return; + + _stream.pause(); + _isPaused = true; + + if (_bytesLoaded == _bytesTotal) { + _timer.stop(); + } + + _isPaused = true; + sendEvent(HtmlMediaEvent.PAUSE); + } + + public function stop():void { + if (_stream == null) + return; + + _stream.close(); + _isPaused = false; + _timer.stop(); + sendEvent(HtmlMediaEvent.STOP); + } + + + public function setCurrentTime(pos:Number):void { + if (_stream == null) { + return; + } + + // Calculate the position of the buffered video + var bufferPosition:Number = _bytesLoaded / _bytesTotal * _duration; + + if (_pseudoStreamingEnabled) { + sendEvent(HtmlMediaEvent.SEEKING); + // Normal seek if it is in buffer and this is the first seek + if (pos < bufferPosition && _seekOffset == 0) { + _stream.seek(pos); + } + else { + // Uses server-side pseudo-streaming to seek + _stream.play(getCurrentUrl(pos)); + _seekOffset = pos; + } + } + else { + sendEvent(HtmlMediaEvent.SEEKING); + _stream.seek(pos); + } + + if (!_isEnded) { + sendEvent(HtmlMediaEvent.TIMEUPDATE); + } + } + + public function setVolume(volume:Number):void { + if (_stream != null) { + _soundTransform = new SoundTransform(volume); + _stream.soundTransform = _soundTransform; + } + + _volume = volume; + + _isMuted = (_volume == 0); + + sendEvent(HtmlMediaEvent.VOLUMECHANGE); + } + + public function getVolume():Number { + if(_isMuted) { + return 0; + } else { + return _volume; + } + } + + public function setMuted(muted:Boolean):void { + + if (_isMuted == muted) + return; + + if (muted) { + _oldVolume = (_stream == null) ? _oldVolume : _stream.soundTransform.volume; + setVolume(0); + } else { + setVolume(_oldVolume); + } + + _isMuted = muted; + } + + + private function sendEvent(eventName:String):void { + + // calculate this to mimic HTML5 + _bufferedTime = _bytesLoaded / _bytesTotal * _duration; + + // build JSON + var values:String = + "duration:" + _duration + + ",framerate:" + _framerate + + ",currentTime:" + currentTime() + + ",muted:" + _isMuted + + ",paused:" + _isPaused + + ",ended:" + _isEnded + + ",volume:" + _volume + + ",src:\"" + _currentUrl + "\"" + + ",bytesTotal:" + _bytesTotal + + ",bufferedBytes:" + _bytesLoaded + + ",bufferedTime:" + _bufferedTime + + ",videoWidth:" + _videoWidth + + ",videoHeight:" + _videoHeight + + ""; + + _element.sendEvent(eventName, values); + } + + private function parseRTMP(url:String):Object { + var match:Array = url.match(/(.*)\/((flv|mp4|mp3):.*)/); + var rtmpInfo:Object = { + server: null, + stream: null + }; + + if (match) { + rtmpInfo.server = match[1]; + rtmpInfo.stream = match[2]; + } + else { + rtmpInfo.server = url.replace(/\/[^\/]+$/,"/"); + rtmpInfo.stream = url.split("/").pop(); + } + + trace("parseRTMP - server: " + rtmpInfo.server + " stream: " + rtmpInfo.stream); + + return rtmpInfo; + } + + private function getCurrentUrl(pos:Number):String { + var url:String = _currentUrl; + if (_pseudoStreamingEnabled) { + if (url.indexOf('?') > -1) { + url = url + '&' + _pseudoStreamingStartQueryParam + '=' + pos; + } + else { + url = url + '?' + _pseudoStreamingStartQueryParam + '=' + pos; + } + } + return url; + } +} +} diff --git a/js/mediaelement/src/flash/htmlelements/YouTubeElement.as b/js/mediaelement/src/flash/htmlelements/YouTubeElement.as new file mode 100644 index 0000000000000000000000000000000000000000..1de3fdd024c637d58f1c0356a692f688fafbae0a --- /dev/null +++ b/js/mediaelement/src/flash/htmlelements/YouTubeElement.as @@ -0,0 +1,403 @@ +package htmlelements +{ + import flash.display.Sprite; + import flash.events.*; + import flash.net.NetConnection; + import flash.net.NetStream; + import flash.media.Video; + import flash.media.SoundTransform; + import flash.utils.Timer; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.net.URLVariables; + import flash.net.URLRequestMethod; + import flash.display.MovieClip; + import flash.display.Loader; + import flash.display.DisplayObject; + + + + import FlashMediaElement; + import HtmlMediaEvent; + + public class YouTubeElement extends Sprite implements IMediaElement + { + private var _currentUrl:String = ""; + private var _autoplay:Boolean = true; + private var _preload:String = ""; + + private var _element:FlashMediaElement; + + // event values + private var _currentTime:Number = 0; + private var _duration:Number = 0; + private var _framerate:Number; + private var _isPaused:Boolean = true; + private var _isEnded:Boolean = false; + private var _volume:Number = 1; + private var _isMuted:Boolean = false; + + private var _bytesLoaded:Number = 0; + private var _bytesTotal:Number = 0; + private var _bufferedTime:Number = 0; + private var _bufferEmpty:Boolean = false; + + private var _videoWidth:Number = -1; + private var _videoHeight:Number = -1; + + private var _timer:Timer; + + // YouTube stuff + private var _playerLoader:Loader; + private var _player:DisplayObject = null; + private var _playerIsLoaded:Boolean = false; + private var _youTubeId:String = ""; + + //http://code.google.com/p/gdata-samples/source/browse/trunk/ytplayer/actionscript3/com/google/youtube/examples/AS3Player.as + private static const WIDESCREEN_ASPECT_RATIO:String = "widescreen"; + private static const QUALITY_TO_PLAYER_WIDTH:Object = { + small: 320, + medium: 640, + large: 854, + hd720: 1280 + }; + private static const STATE_ENDED:Number = 0; + private static const STATE_PLAYING:Number = 1; + private static const STATE_PAUSED:Number = 2; + private static const STATE_CUED:Number = 5; + + + public function get player():DisplayObject { + return _player; + } + + public function setSize(width:Number, height:Number):void { + if (player != null) { + player.setSize(width, height); + } else { + initHeight = height; + initWidth = width; + } + } + + public function get videoHeight():Number { + return _videoHeight; + } + + public function get videoWidth():Number { + return _videoWidth; + } + + + public function duration():Number { + return _duration; + } + + public function currentProgress():Number { + if(_bytesTotal> 0) { + return Math.round(_bytesLoaded/_bytesTotal*100); + } else { + return 0; + } + } + + public function currentTime():Number { + return _currentTime; + } + + + public var initHeight:Number; + public var initWidth:Number; + + // (1) load() + // calls _connection.connect(); + // waits for NetConnection.Connect.Success + // _stream gets created + + private var _isChromeless:Boolean = false; + + + public function YouTubeElement(element:FlashMediaElement, autoplay:Boolean, preload:String, timerRate:Number, startVolume:Number):void + { + _element = element; + _autoplay = autoplay; + _volume = startVolume; + _preload = preload; + initHeight = 0; + initWidth = 0; + + _playerLoader = new Loader(); + _playerLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, playerLoaderInitHandler); + + // chromeless + if (_isChromeless) { + _playerLoader.load(new URLRequest("//www.youtube.com/apiplayer?version=3&controls=1&rel=0&showinfo=0&iv_load_policy=1")); + } + + + _timer = new Timer(timerRate); + _timer.addEventListener("timer", timerHandler); + _timer.start(); + } + + private function playerLoaderInitHandler(event:Event):void { + + trace("yt player init"); + + _element.addChild(_playerLoader.content); + _element.setControlDepth(); + _playerLoader.content.addEventListener("onReady", onPlayerReady); + _playerLoader.content.addEventListener("onError", onPlayerError); + _playerLoader.content.addEventListener("onStateChange", onPlayerStateChange); + _playerLoader.content.addEventListener("onPlaybackQualityChange", onVideoPlaybackQualityChange); + } + + private function onPlayerReady(event:Event):void { + _playerIsLoaded = true; + + _player = _playerLoader.content; + + if (initHeight > 0 && initWidth > 0) + setSize(initWidth, initHeight); + + if (_youTubeId != "") { // && _isChromeless) { + if (_autoplay) { + player.loadVideoById(_youTubeId); + } else { + player.cueVideoById(_youTubeId); + } + _timer.start(); + } + } + + private function onPlayerError(event:Event):void { + // trace("Player error:", Object(event).data); + } + + private function onPlayerStateChange(event:Event):void { + trace("State is", Object(event).data); + + _duration = player.getDuration(); + + switch (Object(event).data) { + case STATE_ENDED: + _isEnded = true; + _isPaused = false; + + sendEvent(HtmlMediaEvent.ENDED); + + break; + + case STATE_PLAYING: + _isEnded = false; + _isPaused = false; + + sendEvent(HtmlMediaEvent.PLAY); + sendEvent(HtmlMediaEvent.PLAYING); + break; + + case STATE_PAUSED: + _isEnded = false; + _isPaused = true; + + sendEvent(HtmlMediaEvent.PAUSE); + + break; + + case STATE_CUED: + sendEvent(HtmlMediaEvent.CANPLAY); + + // resize? + + break; + } + } + + private function onVideoPlaybackQualityChange(event:Event):void { + trace("Current video quality:", Object(event).data); + //resizePlayer(Object(event).data); + } + + private function timerHandler(e:TimerEvent):void { + + if (_playerIsLoaded) { + _bytesLoaded = player.getVideoBytesLoaded(); + _bytesTotal = player.getVideoBytesTotal(); + _currentTime = player.getCurrentTime(); + + if (!_isPaused) + sendEvent(HtmlMediaEvent.TIMEUPDATE); + + if (_bytesLoaded < _bytesTotal) + sendEvent(HtmlMediaEvent.PROGRESS); + } + + } + + private function getYouTubeId(url:String):String { + // http://www.youtube.com/watch?feature=player_embedded&v=yyWWXSwtPP0 + // http://www.youtube.com/v/VIDEO_ID?version=3 + // http://youtu.be/Djd6tPrxc08 + + url = unescape(url); + + var youTubeId:String = ""; + + if (url.indexOf("?") > 0) { + // assuming: http://www.youtube.com/watch?feature=player_embedded&v=yyWWXSwtPP0 + youTubeId = getYouTubeIdFromParam(url); + + // if it's http://www.youtube.com/v/VIDEO_ID?version=3 + if (youTubeId == "") { + youTubeId = getYouTubeIdFromUrl(url); + } + } else { + youTubeId = getYouTubeIdFromUrl(url); + } + + return youTubeId; + } + + // http://www.youtube.com/watch?feature=player_embedded&v=yyWWXSwtPP0 + private function getYouTubeIdFromParam(url:String):String { + + + var youTubeId:String = ""; + var parts:Array = url.split('?'); + var parameters:Array = parts[1].split('&'); + + for (var i:Number=0; i<parameters.length; i++) { + var paramParts:Array = parameters[i].split('='); + if (paramParts[0] == "v") { + + youTubeId = paramParts[1]; + break; + } + + } + + + return youTubeId; + } + + + // http://www.youtube.com/v/VIDEO_ID?version=3 + // http://youtu.be/Djd6tPrxc08 + private function getYouTubeIdFromUrl(url:String):String { + + + var youTubeId:String = ""; + + // remove any querystring elements + var parts:Array = url.split('?'); + url = parts[0]; + + youTubeId = url.substring(url.lastIndexOf("/")+1); + + return youTubeId; + } + + + // interface members + public function setSrc(url:String):void { + trace("yt setSrc()" + url ); + + _currentUrl = url; + + _youTubeId = getYouTubeId(url); + + if (!_playerIsLoaded && !_isChromeless) { + _playerLoader.load(new URLRequest("//www.youtube.com/v/" + _youTubeId + "?version=3&controls=0&rel=0&showinfo=0&iv_load_policy=1")); + } + } + + + + + public function load():void { + // do nothing + trace("yt load()"); + + if (_playerIsLoaded) { + player.loadVideoById(_youTubeId); + _timer.start(); + } else { + /* + if (!_isChromless && _youTubeId != "") { + _playerLoader.load(new URLRequest("http://www.youtube.com/v/" + _youTubeId + "?version=3&controls=0&rel=0&showinfo=0&iv_load_policy=1")); + } + */ + } + } + + public function play():void { + if (_playerIsLoaded) { + player.playVideo(); + } + + } + + public function pause():void { + if (_playerIsLoaded) { + player.pauseVideo(); + } + } + + public function stop():void { + if (_playerIsLoaded) { + player.pauseVideo(); + } + } + + public function setCurrentTime(pos:Number):void { + //_player.seekTo(pos, false); + player.seekTo(pos, true); // works in all places now + } + + public function setVolume(volume:Number):void { + player.setVolume(volume*100); + _volume = volume; + } + + public function getVolume():Number { + return player.getVolume()*100; + } + + public function setMuted(muted:Boolean):void { + if (muted) { + player.mute(); + + } else { + player.unMute(); + } + _isMuted = _player.isMuted(); + sendEvent(HtmlMediaEvent.VOLUMECHANGE); + } + + + private function sendEvent(eventName:String):void { + + // calculate this to mimic HTML5 + _bufferedTime = _bytesLoaded / _bytesTotal * _duration; + + // build JSON + var values:String = + "duration:" + _duration + + ",framerate:" + _framerate + + ",currentTime:" + _currentTime + + ",muted:" + _isMuted + + ",paused:" + _isPaused + + ",ended:" + _isEnded + + ",volume:" + _volume + + ",src:\"" + _currentUrl + "\"" + + ",bytesTotal:" + _bytesTotal + + ",bufferedBytes:" + _bytesLoaded + + ",bufferedTime:" + _bufferedTime + + ",videoWidth:" + _videoWidth + + ",videoHeight:" + _videoHeight + + ""; + + _element.sendEvent(eventName, values); + } + } +} diff --git a/js/mediaelement/src/js/jeesh-extras.js b/js/mediaelement/src/js/jeesh-extras.js new file mode 100644 index 0000000000000000000000000000000000000000..7e662791ac52c5e981949c61c53b2c64d0117164 --- /dev/null +++ b/js/mediaelement/src/js/jeesh-extras.js @@ -0,0 +1,49 @@ +(function($) { + + // borrowed from jQuery (no deep, bad fake object detection) + $.ender({extend: function() { + var options, name, src, copy, + target = arguments[0] || {}, + i = 1, + length = arguments.length; + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && typeof target !== "function" ) { + target = {}; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; + }}); + + // outerWidth + $.ender({outerWidth: function(margin) { + var fp = parseFloat; + return fp(this.width()) + + (margin ? fp(this.css('margin-left')) + fp(this.css('margin-right')) : 0) + + fp(this.css('padding-left'))+ fp(this.css('padding-right')) + + fp(this.css('border-left-width')) + fp(this.css('border-right-width')) + ; + }}, true); + +})(ender); diff --git a/js/mediaelement/src/js/jeesh.js b/js/mediaelement/src/js/jeesh.js new file mode 100644 index 0000000000000000000000000000000000000000..42e3e43db1a70d5b415aa62dc77797f38fdbda51 --- /dev/null +++ b/js/mediaelement/src/js/jeesh.js @@ -0,0 +1,1578 @@ +/*! + * Ender: open module JavaScript framework + * copyright Dustin Diaz & Jacob Thornton 2011 (@ded @fat) + * https://ender.no.de + * License MIT + * Build: ender build jeesh --output jeesh + */ +!function (context) { + + function aug(o, o2) { + for (var k in o2) { + k != 'noConflict' && k != '_VERSION' && (o[k] = o2[k]); + } + return o; + } + + function boosh(s, r, els) { + // string || node || nodelist || window + if (ender._select && (typeof s == 'string' || s.nodeName || s.length && 'item' in s || s == window)) { + els = ender._select(s, r); + els.selector = s; + } else { + els = isFinite(s.length) ? s : [s]; + } + return aug(els, boosh); + } + + function ender(s, r) { + return boosh(s, r); + } + + aug(ender, { + _VERSION: '0.2.4', + ender: function (o, chain) { + aug(chain ? boosh : ender, o); + }, + fn: context.$ && context.$.fn || {} // for easy compat to jQuery plugins + }); + + aug(boosh, { + forEach: function (fn, scope, i) { + // opt out of native forEach so we can intentionally call our own scope + // defaulting to the current item and be able to return self + for (i = 0, l = this.length; i < l; ++i) { + i in this && fn.call(scope || this[i], this[i], i, this); + } + // return self for chaining + return this; + }, + $: ender // handy reference to self + }); + + var old = context.$; + ender.noConflict = function () { + context.$ = old; + return this; + }; + + (typeof module !== 'undefined') && module.exports && (module.exports = ender); + // use subscript notation as extern for Closure compilation + context['ender'] = context['$'] = ender; + +}(this); +/*! + * bean.js - copyright Jacob Thornton 2011 + * https://github.com/fat/bean + * MIT License + * special thanks to: + * dean edwards: http://dean.edwards.name/ + * dperini: https://github.com/dperini/nwevents + * the entire mootools team: github.com/mootools/mootools-core + */ +!function (context) { + var __uid = 1, registry = {}, collected = {}, + overOut = /over|out/, + namespace = /[^\.]*(?=\..*)\.|.*/, + stripName = /\..*/, + addEvent = 'addEventListener', + attachEvent = 'attachEvent', + removeEvent = 'removeEventListener', + detachEvent = 'detachEvent', + doc = context.document || {}, + root = doc.documentElement || {}, + W3C_MODEL = root[addEvent], + eventSupport = W3C_MODEL ? addEvent : attachEvent, + + isDescendant = function (parent, child) { + var node = child.parentNode; + while (node != null) { + if (node == parent) { + return true; + } + node = node.parentNode; + } + }, + + retrieveUid = function (obj, uid) { + return (obj.__uid = uid || obj.__uid || __uid++); + }, + + retrieveEvents = function (element) { + var uid = retrieveUid(element); + return (registry[uid] = registry[uid] || {}); + }, + + listener = W3C_MODEL ? function (element, type, fn, add) { + element[add ? addEvent : removeEvent](type, fn, false); + } : function (element, type, fn, add, custom) { + custom && add && (element['_on' + custom] = element['_on' + custom] || 0); + element[add ? attachEvent : detachEvent]('on' + type, fn); + }, + + nativeHandler = function (element, fn, args) { + return function (event) { + event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || context).event); + return fn.apply(element, [event].concat(args)); + }; + }, + + customHandler = function (element, fn, type, condition, args) { + return function (event) { + if (condition ? condition.call(this, event) : W3C_MODEL ? true : event && event.propertyName == '_on' + type || !event) { + fn.apply(element, [event].concat(args)); + } + }; + }, + + addListener = function (element, orgType, fn, args) { + var type = orgType.replace(stripName, ''), + events = retrieveEvents(element), + handlers = events[type] || (events[type] = {}), + uid = retrieveUid(fn, orgType.replace(namespace, '')); + if (handlers[uid]) { + return element; + } + var custom = customEvents[type]; + if (custom) { + fn = custom.condition ? customHandler(element, fn, type, custom.condition) : fn; + type = custom.base || type; + } + var isNative = nativeEvents[type]; + fn = isNative ? nativeHandler(element, fn, args) : customHandler(element, fn, type, false, args); + isNative = W3C_MODEL || isNative; + if (type == 'unload') { + var org = fn; + fn = function () { + removeListener(element, type, fn) && org(); + }; + } + element[eventSupport] && listener(element, isNative ? type : 'propertychange', fn, true, !isNative && type); + handlers[uid] = fn; + fn.__uid = uid; + return type == 'unload' ? element : (collected[retrieveUid(element)] = element); + }, + + removeListener = function (element, orgType, handler) { + var uid, names, uids, i, events = retrieveEvents(element), type = orgType.replace(stripName, ''); + if (!events || !events[type]) { + return element; + } + names = orgType.replace(namespace, ''); + uids = names ? names.split('.') : [handler.__uid]; + for (i = uids.length; i--;) { + uid = uids[i]; + handler = events[type][uid]; + delete events[type][uid]; + if (element[eventSupport]) { + type = customEvents[type] ? customEvents[type].base : type; + var isNative = W3C_MODEL || nativeEvents[type]; + listener(element, isNative ? type : 'propertychange', handler, false, !isNative && type); + } + } + return element; + }, + + del = function (selector, fn, $) { + return function (e) { + var array = typeof selector == 'string' ? $(selector, this) : selector; + for (var target = e.target; target && target != this; target = target.parentNode) { + for (var i = array.length; i--;) { + if (array[i] == target) { + return fn.apply(target, arguments); + } + } + } + }; + }, + + add = function (element, events, fn, delfn, $) { + if (typeof events == 'object' && !fn) { + for (var type in events) { + events.hasOwnProperty(type) && add(element, type, events[type]); + } + } else { + var isDel = typeof fn == 'string', types = (isDel ? fn : events).split(' '); + fn = isDel ? del(events, delfn, $) : fn; + for (var i = types.length; i--;) { + addListener(element, types[i], fn, Array.prototype.slice.call(arguments, isDel ? 4 : 3)); + } + } + return element; + }, + + remove = function (element, orgEvents, fn) { + var k, type, events, i, + isString = typeof(orgEvents) == 'string', + names = isString && orgEvents.replace(namespace, ''), + rm = removeListener, + attached = retrieveEvents(element); + if (isString && /\s/.test(orgEvents)) { + orgEvents = orgEvents.split(' '); + i = orgEvents.length - 1; + while (remove(element, orgEvents[i]) && i--) {} + return element; + } + events = isString ? orgEvents.replace(stripName, '') : orgEvents; + if (!attached || (isString && !attached[events])) { + return element; + } + if (typeof fn == 'function') { + rm(element, events, fn); + } else if (names) { + rm(element, orgEvents); + } else { + rm = events ? rm : remove; + type = isString && events; + events = events ? (fn || attached[events] || events) : attached; + for (k in events) { + events.hasOwnProperty(k) && rm(element, type || k, events[k]); + } + } + return element; + }, + + fire = function (element, type, args) { + var evt, k, i, types = type.split(' '); + for (i = types.length; i--;) { + type = types[i].replace(stripName, ''); + var isNative = nativeEvents[type], + isNamespace = types[i].replace(namespace, ''), + handlers = retrieveEvents(element)[type]; + if (isNamespace) { + isNamespace = isNamespace.split('.'); + for (k = isNamespace.length; k--;) { + handlers[isNamespace[k]] && handlers[isNamespace[k]].apply(element, args); + } + } else if (!args && element[eventSupport]) { + fireListener(isNative, type, element); + } else { + for (k in handlers) { + handlers.hasOwnProperty(k) && handlers[k].apply(element, args); + } + } + } + return element; + }, + + fireListener = W3C_MODEL ? function (isNative, type, element) { + evt = document.createEvent(isNative ? "HTMLEvents" : "UIEvents"); + evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, context, 1); + element.dispatchEvent(evt); + } : function (isNative, type, element) { + isNative ? element.fireEvent('on' + type, document.createEventObject()) : element['_on' + type]++; + }, + + clone = function (element, from, type) { + var events = retrieveEvents(from), obj, k; + obj = type ? events[type] : events; + for (k in obj) { + obj.hasOwnProperty(k) && (type ? add : clone)(element, type || from, type ? obj[k] : k); + } + return element; + }, + + fixEvent = function (e) { + var result = {}; + if (!e) { + return result; + } + var type = e.type, target = e.target || e.srcElement; + result.preventDefault = fixEvent.preventDefault(e); + result.stopPropagation = fixEvent.stopPropagation(e); + result.target = target && target.nodeType == 3 ? target.parentNode : target; + if (~type.indexOf('key')) { + result.keyCode = e.which || e.keyCode; + } else if ((/click|mouse|menu/i).test(type)) { + result.rightClick = e.which == 3 || e.button == 2; + result.pos = { x: 0, y: 0 }; + if (e.pageX || e.pageY) { + result.clientX = e.pageX; + result.clientY = e.pageY; + } else if (e.clientX || e.clientY) { + result.clientX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + result.clientY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + overOut.test(type) && (result.relatedTarget = e.relatedTarget || e[(type == 'mouseover' ? 'from' : 'to') + 'Element']); + } + for (var k in e) { + if (!(k in result)) { + result[k] = e[k]; + } + } + return result; + }; + + fixEvent.preventDefault = function (e) { + return function () { + if (e.preventDefault) { + e.preventDefault(); + } + else { + e.returnValue = false; + } + }; + }; + + fixEvent.stopPropagation = function (e) { + return function () { + if (e.stopPropagation) { + e.stopPropagation(); + } else { + e.cancelBubble = true; + } + }; + }; + + var nativeEvents = { click: 1, dblclick: 1, mouseup: 1, mousedown: 1, contextmenu: 1, //mouse buttons + mousewheel: 1, DOMMouseScroll: 1, //mouse wheel + mouseover: 1, mouseout: 1, mousemove: 1, selectstart: 1, selectend: 1, //mouse movement + keydown: 1, keypress: 1, keyup: 1, //keyboard + orientationchange: 1, // mobile + touchstart: 1, touchmove: 1, touchend: 1, touchcancel: 1, // touch + gesturestart: 1, gesturechange: 1, gestureend: 1, // gesture + focus: 1, blur: 1, change: 1, reset: 1, select: 1, submit: 1, //form elements + load: 1, unload: 1, beforeunload: 1, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window + error: 1, abort: 1, scroll: 1 }; //misc + + function check(event) { + var related = event.relatedTarget; + if (!related) { + return related == null; + } + return (related != this && related.prefix != 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)); + } + + var customEvents = { + mouseenter: { base: 'mouseover', condition: check }, + mouseleave: { base: 'mouseout', condition: check }, + mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } + }; + + var bean = { add: add, remove: remove, clone: clone, fire: fire }; + + var clean = function (el) { + var uid = remove(el).__uid; + if (uid) { + delete collected[uid]; + delete registry[uid]; + } + }; + + if (context[attachEvent]) { + add(context, 'unload', function () { + for (var k in collected) { + collected.hasOwnProperty(k) && clean(collected[k]); + } + context.CollectGarbage && CollectGarbage(); + }); + } + + var oldBean = context.bean; + bean.noConflict = function () { + context.bean = oldBean; + return this; + }; + + (typeof module !== 'undefined' && module.exports) ? + (module.exports = bean) : + (context['bean'] = bean); + +}(this);!function ($) { + var b = bean.noConflict(), + integrate = function (method, type, method2) { + var _args = type ? [type] : []; + return function () { + for (var args, i = 0, l = this.length; i < l; i++) { + args = [this[i]].concat(_args, Array.prototype.slice.call(arguments, 0)); + args.length == 4 && args.push($); + !arguments.length && method == 'add' && type && (method = 'fire'); + b[method].apply(this, args); + } + return this; + }; + }; + + var add = integrate('add'), + remove = integrate('remove'), + fire = integrate('fire'); + + var methods = { + + on: add, + addListener: add, + bind: add, + listen: add, + delegate: add, + + unbind: remove, + unlisten: remove, + removeListener: remove, + undelegate: remove, + + emit: fire, + trigger: fire, + + cloneEvents: integrate('clone'), + + hover: function (enter, leave, i) { // i for internal + for (i = this.length; i--;) { + b.add.call(this, this[i], 'mouseenter', enter); + b.add.call(this, this[i], 'mouseleave', leave); + } + return this; + } + }; + + var i, shortcuts = [ + 'blur', 'change', 'click', 'dblclick', 'error', 'focus', 'focusin', + 'focusout', 'keydown', 'keypress', 'keyup', 'load', 'mousedown', + 'mouseenter', 'mouseleave', 'mouseout', 'mouseover', 'mouseup', 'mousemove', + 'resize', 'scroll', 'select', 'submit', 'unload' + ]; + + for (i = shortcuts.length; i--;) { + methods[shortcuts[i]] = integrate('add', shortcuts[i]); + } + + $.ender(methods, true); +}(ender); +/*! + * bonzo.js - copyright @dedfat 2011 + * https://github.com/ded/bonzo + * Follow our software http://twitter.com/dedfat + * MIT License + */ +!function (context, win) { + + var doc = context.document, + html = doc.documentElement, + parentNode = 'parentNode', + query = null, + byTag = 'getElementsByTagName', + specialAttributes = /^checked|value|selected$/, + specialTags = /select|fieldset|table|tbody|tfoot|td|tr|colgroup/i, + table = 'table', + tagMap = { thead: table, tbody: table, tfoot: table, tr: 'tbody', th: 'tr', td: 'tr', fieldset: 'form', option: 'select' }, + stateAttributes = /^checked|selected$/, + ie = /msie/i.test(navigator.userAgent), + uidList = [], + uuids = 0, + digit = /^-?[\d\.]+$/, + px = 'px', + // commonly used methods + setAttribute = 'setAttribute', + getAttribute = 'getAttribute', + trimReplace = /(^\s*|\s*$)/g, + unitless = { lineHeight: 1, zoom: 1, zIndex: 1, opacity: 1 }; + + function classReg(c) { + return new RegExp("(^|\\s+)" + c + "(\\s+|$)"); + } + + function each(ar, fn, scope) { + for (var i = 0, l = ar.length; i < l; i++) { + fn.call(scope || ar[i], ar[i], i, ar); + } + return ar; + } + + var trim = String.prototype.trim ? + function (s) { + return s.trim(); + } : + function (s) { + return s.replace(trimReplace, ''); + }; + + function camelize(s) { + return s.replace(/-(.)/g, function (m, m1) { + return m1.toUpperCase(); + }); + } + + function is(node) { + return node && node.nodeName && node.nodeType == 1; + } + + function some(ar, fn, scope) { + for (var i = 0, j = ar.length; i < j; ++i) { + if (fn.call(scope, ar[i], i, ar)) { + return true; + } + } + return false; + } + + var getStyle = doc.defaultView && doc.defaultView.getComputedStyle ? + function (el, property) { + var value = null; + if (property == 'float') { + property = 'cssFloat'; + } + var computed = doc.defaultView.getComputedStyle(el, ''); + computed && (value = computed[camelize(property)]); + return el.style[property] || value; + + } : (ie && html.currentStyle) ? + + function (el, property) { + property = camelize(property); + property = property == 'float' ? 'styleFloat' : property; + + if (property == 'opacity') { + var val = 100; + try { + val = el.filters['DXImageTransform.Microsoft.Alpha'].opacity; + } catch (e1) { + try { + val = el.filters('alpha').opacity; + } catch (e2) {} + } + return val / 100; + } + var value = el.currentStyle ? el.currentStyle[property] : null; + return el.style[property] || value; + } : + + function (el, property) { + return el.style[camelize(property)]; + }; + + function insert(target, host, fn) { + var i = 0, self = host || this, r = [], + nodes = query && typeof target == 'string' && target.charAt(0) != '<' ? function (n) { + return (n = query(target)) && (n.selected = 1) && n; + }() : target; + each(normalize(nodes), function (t) { + each(self, function (el) { + var n = !el[parentNode] || (el[parentNode] && !el[parentNode][parentNode]) ? + function () { + var c = el.cloneNode(true); + self.$ && self.cloneEvents && self.$(c).cloneEvents(el); + return c; + }() : + el; + fn(t, n); + r[i] = n; + i++; + }); + }, this); + each(r, function (e, i) { + self[i] = e; + }); + self.length = i; + return self; + } + + function xy(el, x, y) { + var $el = bonzo(el), + style = $el.css('position'), + offset = $el.offset(), + rel = 'relative', + isRel = style == rel, + delta = [parseInt($el.css('left'), 10), parseInt($el.css('top'), 10)]; + + if (style == 'static') { + $el.css('position', rel); + style = rel; + } + + isNaN(delta[0]) && (delta[0] = isRel ? 0 : el.offsetLeft); + isNaN(delta[1]) && (delta[1] = isRel ? 0 : el.offsetTop); + + x !== null && (el.style.left = x - offset.left + delta[0] + 'px'); + y !== null && (el.style.top = y - offset.top + delta[1] + 'px'); + + } + + function Bonzo(elements) { + this.length = 0; + if (elements) { + elements = typeof elements !== 'string' && + !elements.nodeType && + typeof elements.length !== 'undefined' ? + elements : + [elements]; + this.length = elements.length; + for (var i = 0; i < elements.length; i++) { + this[i] = elements[i]; + } + } + } + + Bonzo.prototype = { + + each: function (fn, scope) { + return each(this, fn, scope); + }, + + map: function (fn, reject) { + var m = [], n, i; + for (i = 0; i < this.length; i++) { + n = fn.call(this, this[i]); + reject ? (reject(n) && m.push(n)) : m.push(n); + } + return m; + }, + + first: function () { + return bonzo(this[0]); + }, + + last: function () { + return bonzo(this[this.length - 1]); + }, + + html: function (h, text) { + var method = text ? + html.textContent == null ? + 'innerText' : + 'textContent' : + 'innerHTML', m; + function append(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } + each(normalize(h), function (node) { + el.appendChild(node); + }); + } + return typeof h !== 'undefined' ? + this.each(function (el) { + (m = el.tagName.match(specialTags)) ? + append(el, m[0]) : + (el[method] = h); + }) : + this[0] ? this[0][method] : ''; + }, + + text: function (text) { + return this.html(text, 1); + }, + + addClass: function (c) { + return this.each(function (el) { + this.hasClass(el, c) || (el.className = trim(el.className + ' ' + c)); + }, this); + }, + + removeClass: function (c) { + return this.each(function (el) { + this.hasClass(el, c) && (el.className = trim(el.className.replace(classReg(c), ' '))); + }, this); + }, + + hasClass: function (el, c) { + return typeof c == 'undefined' ? + some(this, function (i) { + return classReg(el).test(i.className); + }) : + classReg(c).test(el.className); + }, + + toggleClass: function (c, condition) { + if (typeof condition !== 'undefined' && !condition) { + return this; + } + return this.each(function (el) { + this.hasClass(el, c) ? + (el.className = trim(el.className.replace(classReg(c), ' '))) : + (el.className = trim(el.className + ' ' + c)); + }, this); + }, + + show: function (type) { + return this.each(function (el) { + el.style.display = type || ''; + }); + }, + + hide: function (elements) { + return this.each(function (el) { + el.style.display = 'none'; + }); + }, + + append: function (node) { + return this.each(function (el) { + each(normalize(node), function (i) { + el.appendChild(i); + }); + }); + }, + + prepend: function (node) { + return this.each(function (el) { + var first = el.firstChild; + each(normalize(node), function (i) { + el.insertBefore(i, first); + }); + }); + }, + + appendTo: function (target, host) { + return insert.call(this, target, host, function (t, el) { + t.appendChild(el); + }); + }, + + prependTo: function (target, host) { + return insert.call(this, target, host, function (t, el) { + t.insertBefore(el, t.firstChild); + }); + }, + + next: function () { + return this.related('nextSibling'); + }, + + previous: function () { + return this.related('previousSibling'); + }, + + related: function (method) { + return this.map( + function (el) { + el = el[method]; + while (el && el.nodeType !== 1) { + el = el[method]; + } + return el || 0; + }, + function (el) { + return el; + } + ); + }, + + before: function (node) { + return this.each(function (el) { + each(bonzo.create(node), function (i) { + el[parentNode].insertBefore(i, el); + }); + }); + }, + + after: function (node) { + return this.each(function (el) { + each(bonzo.create(node), function (i) { + el[parentNode].insertBefore(i, el.nextSibling); + }); + }); + }, + + insertBefore: function (target, host) { + return insert.call(this, target, host, function (t, el) { + t[parentNode].insertBefore(el, t); + }); + }, + + insertAfter: function (target, host) { + return insert.call(this, target, host, function (t, el) { + var sibling = t.nextSibling; + if (sibling) { + t[parentNode].insertBefore(el, sibling); + } + else { + t[parentNode].appendChild(el); + } + }); + }, + + css: function (o, v, p) { + // is this a request for just getting a style? + if (v === undefined && typeof o == 'string') { + // repurpose 'v' + v = this[0]; + if (!v) { + return null; + } + if (v == doc || v == win) { + p = (v == doc) ? bonzo.doc() : bonzo.viewport(); + return o == 'width' ? p.width : + o == 'height' ? p.height : ''; + } + return getStyle(v, o); + } + var iter = o; + if (typeof o == 'string') { + iter = {}; + iter[o] = v; + } + + if (ie && iter.opacity) { + // oh this 'ol gamut + iter.filter = 'alpha(opacity=' + (iter.opacity * 100) + ')'; + // give it layout + iter.zoom = o.zoom || 1; + delete iter.opacity; + } + + if (v = iter['float']) { + // float is a reserved style word. w3 uses cssFloat, ie uses styleFloat + ie ? (iter.styleFloat = v) : (iter.cssFloat = v); + delete iter['float']; + } + + var fn = function (el, p, v) { + for (var k in iter) { + if (iter.hasOwnProperty(k)) { + v = iter[k]; + // change "5" to "5px" - unless you're line-height, which is allowed + (p = camelize(k)) && digit.test(v) && !(p in unitless) && (v += px); + el.style[p] = v; + } + } + }; + return this.each(fn); + }, + + offset: function (x, y) { + if (x || y) { + return this.each(function (el) { + xy(el, x, y); + }); + } + var el = this[0]; + var width = el.offsetWidth; + var height = el.offsetHeight; + var top = el.offsetTop; + var left = el.offsetLeft; + while (el = el.offsetParent) { + top = top + el.offsetTop; + left = left + el.offsetLeft; + } + + return { + top: top, + left: left, + height: height, + width: width + }; + }, + + attr: function (k, v) { + var el = this[0]; + return typeof v == 'undefined' ? + specialAttributes.test(k) ? + stateAttributes.test(k) && typeof el[k] == 'string' ? + true : el[k] : el[getAttribute](k) : + this.each(function (el) { + k == 'value' ? (el.value = v) : el[setAttribute](k, v); + }); + }, + + val: function (s) { + return (typeof s == 'string') ? this.attr('value', s) : this[0].value; + }, + + removeAttr: function (k) { + return this.each(function (el) { + el.removeAttribute(k); + }); + }, + + data: function (k, v) { + var el = this[0]; + if (typeof v === 'undefined') { + el[getAttribute]('data-node-uid') || el[setAttribute]('data-node-uid', ++uuids); + var uid = el[getAttribute]('data-node-uid'); + uidList[uid] || (uidList[uid] = {}); + return uidList[uid][k]; + } else { + return this.each(function (el) { + el[getAttribute]('data-node-uid') || el[setAttribute]('data-node-uid', ++uuids); + var uid = el[getAttribute]('data-node-uid'); + var o = {}; + o[k] = v; + uidList[uid] = o; + }); + } + }, + + remove: function () { + return this.each(function (el) { + el[parentNode] && el[parentNode].removeChild(el); + }); + }, + + empty: function () { + return this.each(function (el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } + }); + }, + + detach: function () { + return this.map(function (el) { + return el[parentNode].removeChild(el); + }); + }, + + scrollTop: function (y) { + return scroll.call(this, null, y, 'y'); + }, + + scrollLeft: function (x) { + return scroll.call(this, x, null, 'x'); + } + }; + + function normalize(node) { + return typeof node == 'string' ? bonzo.create(node) : is(node) ? [node] : node; // assume [nodes] + } + + function scroll(x, y, type) { + var el = this[0]; + if (x == null && y == null) { + return (isBody(el) ? getWindowScroll() : { x: el.scrollLeft, y: el.scrollTop })[type]; + } + if (isBody(el)) { + win.scrollTo(x, y); + } else { + x != null && (el.scrollLeft = x); + y != null && (el.scrollTop = y); + } + return this; + } + + function isBody(element) { + return element === win || (/^(?:body|html)$/i).test(element.tagName); + } + + function getWindowScroll() { + return { x: win.pageXOffset || html.scrollLeft, y: win.pageYOffset || html.scrollTop }; + } + + function bonzo(els, host) { + return new Bonzo(els, host); + } + + bonzo.setQueryEngine = function (q) { + query = q; + delete bonzo.setQueryEngine; + }; + + bonzo.aug = function (o, target) { + for (var k in o) { + o.hasOwnProperty(k) && ((target || Bonzo.prototype)[k] = o[k]); + } + }; + + bonzo.create = function (node) { + return typeof node == 'string' ? + function () { + var tag = /^<([^\s>]+)/.exec(node); + var el = doc.createElement(tag && tagMap[tag[1].toLowerCase()] || 'div'), els = []; + el.innerHTML = node; + var nodes = el.childNodes; + el = el.firstChild; + els.push(el); + while (el = el.nextSibling) { + (el.nodeType == 1) && els.push(el); + } + return els; + + }() : is(node) ? [node.cloneNode(true)] : []; + }; + + bonzo.doc = function () { + var w = html.scrollWidth, + h = html.scrollHeight, + vp = this.viewport(); + return { + width: Math.max(w, vp.width), + height: Math.max(h, vp.height) + }; + }; + + bonzo.firstChild = function (el) { + for (var c = el.childNodes, i = 0, j = (c && c.length) || 0, e; i < j; i++) { + if (c[i].nodeType === 1) { + e = c[j = i]; + } + } + return e; + }; + + bonzo.viewport = function () { + var h = self.innerHeight, + w = self.innerWidth; + if (ie) { + h = html.clientHeight; + w = html.clientWidth; + } + return { + width: w, + height: h + }; + }; + + bonzo.isAncestor = 'compareDocumentPosition' in html ? + function (container, element) { + return (container.compareDocumentPosition(element) & 16) == 16; + } : 'contains' in html ? + function (container, element) { + return container !== element && container.contains(element); + } : + function (container, element) { + while (element = element[parentNode]) { + if (element === container) { + return true; + } + } + return false; + }; + + var old = context.bonzo; + bonzo.noConflict = function () { + context.bonzo = old; + return this; + }; + context['bonzo'] = bonzo; + +}(this, window);!function ($) { + + var b = bonzo; + b.setQueryEngine($); + $.ender(b); + $.ender(b(), true); + $.ender({ + create: function (node) { + return $(b.create(node)); + } + }); + + $.id = function (id) { + return $([document.getElementById(id)]); + }; + + function indexOf(ar, val) { + for (var i = 0; i < ar.length; i++) { + if (ar[i] === val) { + return i; + } + } + return -1; + } + + function uniq(ar) { + var a = [], i, j; + label: + for (i = 0; i < ar.length; i++) { + for (j = 0; j < a.length; j++) { + if (a[j] == ar[i]) { + continue label; + } + } + a[a.length] = ar[i]; + } + return a; + } + + $.ender({ + parents: function (selector, closest) { + var collection = $(selector), j, k, p, r = []; + for (j = 0, k = this.length; j < k; j++) { + p = this[j]; + while (p = p.parentNode) { + if (indexOf(collection, p) !== -1) { + r.push(p); + if (closest) break; + } + } + } + return $(uniq(r)); + }, + + closest: function (selector) { + return this.parents(selector, true); + }, + + first: function () { + return $(this[0]); + }, + + last: function () { + return $(this[this.length - 1]); + }, + + next: function () { + return $(b(this).next()); + }, + + previous: function () { + return $(b(this).previous()); + }, + + appendTo: function (t) { + return b(this.selector).appendTo(t, this); + }, + + prependTo: function (t) { + return b(this.selector).prependTo(t, this); + }, + + insertAfter: function (t) { + return b(this.selector).insertAfter(t, this); + }, + + insertBefore: function (t) { + return b(this.selector).insertBefore(t, this); + }, + + siblings: function () { + var i, l, p, r = []; + for (i = 0, l = this.length; i < l; i++) { + p = this[i]; + while (p = p.previousSibling) { + p.nodeType == 1 && r.push(p); + } + p = this[i]; + while (p = p.nextSibling) { + p.nodeType == 1 && r.push(p); + } + } + return $(r); + }, + + children: function () { + var i, el, r = []; + for (i = 0, l = this.length; i < l; i++) { + if (!(el = b.firstChild(this[i]))) { + continue; + } + r.push(el); + while (el = el.nextSibling) { + el.nodeType == 1 && r.push(el); + } + } + return $(uniq(r)); + }, + + height: function (v) { + return dimension(v, this, 'height') + }, + + width: function (v) { + return dimension(v, this, 'width') + } + }, true); + + function dimension(v, self, which) { + return v ? + self.css(which, v) : + function (r) { + r = parseInt(self.css(which), 10); + return isNaN(r) ? self[0]['offset' + which.replace(/^\w/, function (m) {return m.toUpperCase()})] : r + }() + } + +}(ender || $); + +!function (context, doc) { + var fns = [], ol, fn, f = false, + testEl = doc.documentElement, + hack = testEl.doScroll, + domContentLoaded = 'DOMContentLoaded', + addEventListener = 'addEventListener', + onreadystatechange = 'onreadystatechange', + loaded = /^loade|c/.test(doc.readyState); + + function flush(i) { + loaded = 1; + while (i = fns.shift()) { i() } + } + doc[addEventListener] && doc[addEventListener](domContentLoaded, fn = function () { + doc.removeEventListener(domContentLoaded, fn, f); + flush(); + }, f); + + + hack && doc.attachEvent(onreadystatechange, (ol = function () { + if (/^c/.test(doc.readyState)) { + doc.detachEvent(onreadystatechange, ol); + flush(); + } + })); + + context['domReady'] = hack ? + function (fn) { + self != top ? + loaded ? fn() : fns.push(fn) : + function () { + try { + testEl.doScroll('left'); + } catch (e) { + return setTimeout(function() { domReady(fn) }, 50); + } + fn(); + }() + } : + function (fn) { + loaded ? fn() : fns.push(fn); + }; + +}(this, document);!function ($) { + $.ender({domReady: domReady}); + $.ender({ + ready: function (f) { + domReady(f); + return this; + } + }, true); +}(ender); +/*! + * Qwery - A Blazing Fast query selector engine + * https://github.com/ded/qwery + * copyright Dustin Diaz & Jacob Thornton 2011 + * MIT License + */ + +!function (context, doc) { + + var c, i, j, k, l, m, o, p, r, v, + el, node, len, found, classes, item, items, token, + html = doc.documentElement, + id = /#([\w\-]+)/, + clas = /\.[\w\-]+/g, + idOnly = /^#([\w\-]+$)/, + classOnly = /^\.([\w\-]+)$/, + tagOnly = /^([\w\-]+)$/, + tagAndOrClass = /^([\w]+)?\.([\w\-]+)$/, + normalizr = /\s*([\s\+\~>])\s*/g, + splitters = /[\s\>\+\~]/, + splittersMore = /(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\])/, + dividers = new RegExp('(' + splitters.source + ')' + splittersMore.source, 'g'), + tokenizr = new RegExp(splitters.source + splittersMore.source), + specialChars = /([.*+?\^=!:${}()|\[\]\/\\])/g, + simple = /^([a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/, + attr = /\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/, + pseudo = /:([\w\-]+)(\(['"]?(\w+)['"]?\))?/, + chunker = new RegExp(simple.source + '(' + attr.source + ')?' + '(' + pseudo.source + ')?'), + walker = { + ' ': function (node) { + return node && node !== html && node.parentNode + }, + '>': function (node, contestant) { + return node && node.parentNode == contestant.parentNode && node.parentNode; + }, + '~': function (node) { + return node && node.previousSibling; + }, + '+': function (node, contestant, p1, p2) { + if (!node) { + return false; + } + p1 = previous(node); + p2 = previous(contestant); + return p1 && p2 && p1 == p2 && p1; + } + }; + window.tokenizr = tokenizr; + window.dividers = dividers; + function cache() { + this.c = {}; + } + cache.prototype = { + g: function (k) { + return this.c[k] || undefined; + }, + s: function (k, v) { + this.c[k] = v; + return v; + } + }; + + var classCache = new cache(), + cleanCache = new cache(), + attrCache = new cache(), + tokenCache = new cache(); + + function array(ar) { + r = []; + for (i = 0, len = ar.length; i < len; i++) { + r[i] = ar[i]; + } + return r; + } + + function previous(n) { + while (n = n.previousSibling) { + if (n.nodeType == 1) { + break; + } + } + return n + } + + function q(query) { + return query.match(chunker); + } + + // this next method expect at most these args + // given => div.hello[title="world"]:foo('bar') + + // div.hello[title="world"]:foo('bar'), div, .hello, [title="world"], title, =, world, :foo('bar'), foo, ('bar'), bar] + + function interpret(whole, tag, idsAndClasses, wholeAttribute, attribute, qualifier, value, wholePseudo, pseudo, wholePseudoVal, pseudoVal) { + var m, c, k; + if (tag && this.tagName.toLowerCase() !== tag) { + return false; + } + if (idsAndClasses && (m = idsAndClasses.match(id)) && m[1] !== this.id) { + return false; + } + if (idsAndClasses && (classes = idsAndClasses.match(clas))) { + for (i = classes.length; i--;) { + c = classes[i].slice(1); + if (!(classCache.g(c) || classCache.s(c, new RegExp('(^|\\s+)' + c + '(\\s+|$)'))).test(this.className)) { + return false; + } + } + } + if (pseudo && qwery.pseudos[pseudo] && !qwery.pseudos[pseudo](this, pseudoVal)) { + return false; + } + if (wholeAttribute && !value) { + o = this.attributes; + for (k in o) { + if (Object.prototype.hasOwnProperty.call(o, k) && (o[k].name || k) == attribute) { + return this; + } + } + } + if (wholeAttribute && !checkAttr(qualifier, this.getAttribute(attribute) || '', value)) { + return false; + } + return this; + } + + function clean(s) { + return cleanCache.g(s) || cleanCache.s(s, s.replace(specialChars, '\\$1')); + } + + function checkAttr(qualify, actual, val) { + switch (qualify) { + case '=': + return actual == val; + case '^=': + return actual.match(attrCache.g('^=' + val) || attrCache.s('^=' + val, new RegExp('^' + clean(val)))); + case '$=': + return actual.match(attrCache.g('$=' + val) || attrCache.s('$=' + val, new RegExp(clean(val) + '$'))); + case '*=': + return actual.match(attrCache.g(val) || attrCache.s(val, new RegExp(clean(val)))); + case '~=': + return actual.match(attrCache.g('~=' + val) || attrCache.s('~=' + val, new RegExp('(?:^|\\s+)' + clean(val) + '(?:\\s+|$)'))); + case '|=': + return actual.match(attrCache.g('|=' + val) || attrCache.s('|=' + val, new RegExp('^' + clean(val) + '(-|$)'))); + } + return 0; + } + + function _qwery(selector) { + var r = [], ret = [], i, j = 0, k, l, m, p, token, tag, els, root, intr, item, children, + tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr)), + dividedTokens = selector.match(dividers), dividedToken; + tokens = tokens.slice(0); // this makes a copy of the array so the cached original is not effected + if (!tokens.length) { + return r; + } + + token = tokens.pop(); + root = tokens.length && (m = tokens[tokens.length - 1].match(idOnly)) ? doc.getElementById(m[1]) : doc; + if (!root) { + return r; + } + intr = q(token); + els = dividedTokens && /^[+~]$/.test(dividedTokens[dividedTokens.length - 1]) ? function (r) { + while (root = root.nextSibling) { + root.nodeType == 1 && (intr[1] ? intr[1] == root.tagName.toLowerCase() : 1) && r.push(root) + } + return r + }([]) : + root.getElementsByTagName(intr[1] || '*'); + for (i = 0, l = els.length; i < l; i++) { + if (item = interpret.apply(els[i], intr)) { + r[j++] = item; + } + } + if (!tokens.length) { + return r; + } + + // loop through all descendent tokens + for (j = 0, l = r.length, k = 0; j < l; j++) { + p = r[j]; + // loop through each token backwards crawling up tree + for (i = tokens.length; i--;) { + // loop through parent nodes + while (p = walker[dividedTokens[i]](p, r[j])) { + if (found = interpret.apply(p, q(tokens[i]))) { + break; + } + } + } + found && (ret[k++] = r[j]); + } + return ret; + } + + function boilerPlate(selector, _root, fn) { + var root = (typeof _root == 'string') ? fn(_root)[0] : (_root || doc); + if (selector === window || isNode(selector)) { + return !_root || (selector !== window && isNode(root) && isAncestor(selector, root)) ? [selector] : []; + } + if (selector && typeof selector === 'object' && isFinite(selector.length)) { + return array(selector); + } + if (m = selector.match(idOnly)) { + return (el = doc.getElementById(m[1])) ? [el] : []; + } + if (m = selector.match(tagOnly)) { + return array(root.getElementsByTagName(m[1])); + } + return false; + } + + function isNode(el) { + return (el && el.nodeType && (el.nodeType == 1 || el.nodeType == 9)); + } + + function uniq(ar) { + var a = [], i, j; + label: + for (i = 0; i < ar.length; i++) { + for (j = 0; j < a.length; j++) { + if (a[j] == ar[i]) { + continue label; + } + } + a[a.length] = ar[i]; + } + return a; + } + + function qwery(selector, _root) { + var root = (typeof _root == 'string') ? qwery(_root)[0] : (_root || doc); + if (!root || !selector) { + return []; + } + if (m = boilerPlate(selector, _root, qwery)) { + return m; + } + return select(selector, root); + } + + var isAncestor = 'compareDocumentPosition' in html ? + function (element, container) { + return (container.compareDocumentPosition(element) & 16) == 16; + } : 'contains' in html ? + function (element, container) { + container = container == doc || container == window ? html : container; + return container !== element && container.contains(element); + } : + function (element, container) { + while (element = element.parentNode) { + if (element === container) { + return 1; + } + } + return 0; + }, + + select = (doc.querySelector && doc.querySelectorAll) ? + function (selector, root) { + if (doc.getElementsByClassName && (m = selector.match(classOnly))) { + return array((root).getElementsByClassName(m[1])); + } + return array((root).querySelectorAll(selector)); + } : + function (selector, root) { + selector = selector.replace(normalizr, '$1'); + var result = [], collection, collections = [], i; + if (m = selector.match(tagAndOrClass)) { + items = root.getElementsByTagName(m[1] || '*'); + r = classCache.g(m[2]) || classCache.s(m[2], new RegExp('(^|\\s+)' + m[2] + '(\\s+|$)')); + for (i = 0, l = items.length, j = 0; i < l; i++) { + r.test(items[i].className) && (result[j++] = items[i]); + } + return result; + } + for (i = 0, items = selector.split(','), l = items.length; i < l; i++) { + collections[i] = _qwery(items[i]); + } + for (i = 0, l = collections.length; i < l && (collection = collections[i]); i++) { + var ret = collection; + if (root !== doc) { + ret = []; + for (j = 0, m = collection.length; j < m && (element = collection[j]); j++) { + // make sure element is a descendent of root + isAncestor(element, root) && ret.push(element); + } + } + result = result.concat(ret); + } + return uniq(result); + }; + + qwery.uniq = uniq; + qwery.pseudos = {}; + + var oldQwery = context.qwery; + qwery.noConflict = function () { + context.qwery = oldQwery; + return this; + }; + context['qwery'] = qwery; + +}(this, document);!function (doc) { + var q = qwery.noConflict(); + var table = 'table', + nodeMap = { + thead: table, + tbody: table, + tfoot: table, + tr: 'tbody', + th: 'tr', + td: 'tr', + fieldset: 'form', + option: 'select' + } + function create(node, root) { + var tag = /^<([^\s>]+)/.exec(node)[1] + var el = (root || doc).createElement(nodeMap[tag] || 'div'), els = []; + el.innerHTML = node; + var nodes = el.childNodes; + el = el.firstChild; + els.push(el); + while (el = el.nextSibling) { + (el.nodeType == 1) && els.push(el); + } + return els; + } + $._select = function (s, r) { + return /^\s*</.test(s) ? create(s, r) : q(s, r); + }; + $.ender({ + find: function (s) { + var r = [], i, l, j, k, els; + for (i = 0, l = this.length; i < l; i++) { + els = q(s, this[i]); + for (j = 0, k = els.length; j < k; j++) { + r.push(els[j]); + } + } + return $(q.uniq(r)); + } + , and: function (s) { + var plus = $(s); + for (var i = this.length, j = 0, l = this.length + plus.length; i < l; i++, j++) { + this[i] = plus[j]; + } + return this; + } + }, true); +}(document); diff --git a/js/mediaelement/src/js/me-featuredetection.js b/js/mediaelement/src/js/me-featuredetection.js new file mode 100644 index 0000000000000000000000000000000000000000..d676bd0f4ac4658e3d492e4c3f42600e921e4e87 --- /dev/null +++ b/js/mediaelement/src/js/me-featuredetection.js @@ -0,0 +1,137 @@ +// necessary detection (fixes for <IE9) +mejs.MediaFeatures = { + init: function() { + var + t = this, + d = document, + nav = mejs.PluginDetector.nav, + ua = mejs.PluginDetector.ua.toLowerCase(), + i, + v, + html5Elements = ['source','track','audio','video']; + + // detect browsers (only the ones that have some kind of quirk we need to work around) + t.isiPad = (ua.match(/ipad/i) !== null); + t.isiPhone = (ua.match(/iphone/i) !== null); + t.isiOS = t.isiPhone || t.isiPad; + t.isAndroid = (ua.match(/android/i) !== null); + t.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null); + t.isBustedNativeHTTPS = (location.protocol === 'https:' && (ua.match(/android [12]\./) !== null || ua.match(/macintosh.* version.* safari/) !== null)); + t.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1 || nav.appName.toLowerCase().match(/trident/gi) !== null); + t.isChrome = (ua.match(/chrome/gi) !== null); + t.isChromium = (ua.match(/chromium/gi) !== null); + t.isFirefox = (ua.match(/firefox/gi) !== null); + t.isWebkit = (ua.match(/webkit/gi) !== null); + t.isGecko = (ua.match(/gecko/gi) !== null) && !t.isWebkit && !t.isIE; + t.isOpera = (ua.match(/opera/gi) !== null); + t.hasTouch = ('ontouchstart' in window); // && window.ontouchstart != null); // this breaks iOS 7 + + // borrowed from Modernizr + t.svg = !! document.createElementNS && + !! document.createElementNS('http://www.w3.org/2000/svg','svg').createSVGRect; + + // create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection + for (i=0; i<html5Elements.length; i++) { + v = document.createElement(html5Elements[i]); + } + + t.supportsMediaTag = (typeof v.canPlayType !== 'undefined' || t.isBustedAndroid); + + // Fix for IE9 on Windows 7N / Windows 7KN (Media Player not installer) + try{ + v.canPlayType("video/mp4"); + }catch(e){ + t.supportsMediaTag = false; + } + + // detect native JavaScript fullscreen (Safari/Firefox only, Chrome still fails) + + // iOS + t.hasSemiNativeFullScreen = (typeof v.webkitEnterFullscreen !== 'undefined'); + + // W3C + t.hasNativeFullscreen = (typeof v.requestFullscreen !== 'undefined'); + + // webkit/firefox/IE11+ + t.hasWebkitNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined'); + t.hasMozNativeFullScreen = (typeof v.mozRequestFullScreen !== 'undefined'); + t.hasMsNativeFullScreen = (typeof v.msRequestFullscreen !== 'undefined'); + + t.hasTrueNativeFullScreen = (t.hasWebkitNativeFullScreen || t.hasMozNativeFullScreen || t.hasMsNativeFullScreen); + t.nativeFullScreenEnabled = t.hasTrueNativeFullScreen; + + // Enabled? + if (t.hasMozNativeFullScreen) { + t.nativeFullScreenEnabled = document.mozFullScreenEnabled; + } else if (t.hasMsNativeFullScreen) { + t.nativeFullScreenEnabled = document.msFullscreenEnabled; + } + + if (t.isChrome) { + t.hasSemiNativeFullScreen = false; + } + + if (t.hasTrueNativeFullScreen) { + + t.fullScreenEventName = ''; + if (t.hasWebkitNativeFullScreen) { + t.fullScreenEventName = 'webkitfullscreenchange'; + + } else if (t.hasMozNativeFullScreen) { + t.fullScreenEventName = 'mozfullscreenchange'; + + } else if (t.hasMsNativeFullScreen) { + t.fullScreenEventName = 'MSFullscreenChange'; + } + + t.isFullScreen = function() { + if (t.hasMozNativeFullScreen) { + return d.mozFullScreen; + + } else if (t.hasWebkitNativeFullScreen) { + return d.webkitIsFullScreen; + + } else if (t.hasMsNativeFullScreen) { + return d.msFullscreenElement !== null; + } + } + + t.requestFullScreen = function(el) { + + if (t.hasWebkitNativeFullScreen) { + el.webkitRequestFullScreen(); + + } else if (t.hasMozNativeFullScreen) { + el.mozRequestFullScreen(); + + } else if (t.hasMsNativeFullScreen) { + el.msRequestFullscreen(); + + } + } + + t.cancelFullScreen = function() { + if (t.hasWebkitNativeFullScreen) { + document.webkitCancelFullScreen(); + + } else if (t.hasMozNativeFullScreen) { + document.mozCancelFullScreen(); + + } else if (t.hasMsNativeFullScreen) { + document.msExitFullscreen(); + + } + } + + } + + + // OS X 10.5 can't do this even if it says it can :( + if (t.hasSemiNativeFullScreen && ua.match(/mac os x 10_5/i)) { + t.hasNativeFullScreen = false; + t.hasSemiNativeFullScreen = false; + } + + } +}; +mejs.MediaFeatures.init(); diff --git a/js/mediaelement/src/js/me-header.js b/js/mediaelement/src/js/me-header.js new file mode 100644 index 0000000000000000000000000000000000000000..d6d75f75f4c908099362e986811a9db13281384f --- /dev/null +++ b/js/mediaelement/src/js/me-header.js @@ -0,0 +1,14 @@ +/*! + * + * MediaElement.js + * HTML5 <video> and <audio> shim and player + * http://mediaelementjs.com/ + * + * Creates a JavaScript object that mimics HTML5 MediaElement API + * for browsers that don't understand HTML5 or can't play the provided codec + * Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3 + * + * Copyright 2010-2014, John Dyer (http://j.hn) + * License: MIT + * + */ \ No newline at end of file diff --git a/js/mediaelement/src/js/me-i18n-locale-de.js b/js/mediaelement/src/js/me-i18n-locale-de.js new file mode 100644 index 0000000000000000000000000000000000000000..956d0ad631dc88272a06affb14488c41ca77a310 --- /dev/null +++ b/js/mediaelement/src/js/me-i18n-locale-de.js @@ -0,0 +1,28 @@ +/* + * This is a i18n.locale language object. + * + * German translation by Tim Latz, latz.tim@gmail.com + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + if (typeof exports.de === 'undefined') { + exports.de = { + "Fullscreen" : "Vollbild", + "Go Fullscreen" : "Vollbild an", + "Turn off Fullscreen" : "Vollbild aus", + "Close" : "Schließen" + }; + } + +}(mejs.i18n.locale.strings)); \ No newline at end of file diff --git a/js/mediaelement/src/js/me-i18n-locale-fr.js b/js/mediaelement/src/js/me-i18n-locale-fr.js new file mode 100644 index 0000000000000000000000000000000000000000..343b53db02cb906bd333a1faefccb996f1863c3d --- /dev/null +++ b/js/mediaelement/src/js/me-i18n-locale-fr.js @@ -0,0 +1,35 @@ +/* + * This is a i18n.locale language object. + * + * French translation by Luc Poupard, Twitter: @klohFR + * @author + * Luc Poupard (Twitter: @klohFR) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + if (typeof exports.fr === 'undefined') { + exports.fr = { + "Download File" : "Télécharger le fichier", + "Play/Pause" : "Lecture/Pause", + "Mute Toggle" : "Activer/désactiver le son", + "Fullscreen" : "Plein écran", + "Captions/Subtitles" : "Sous-titres", + "None" : "Aucun", + "Go Fullscreen" : "Afficher en plein écran", + "Turn off Fullscreen" : "Quitter le mode plein écran", + "Unmute" : "Activer le son", + "Mute" : "Désactiver le son", + "Download Video" : "Télécharger la vidéo", + "Close" : "Fermer" + }; + } + +}(mejs.i18n.locale.strings)); diff --git a/js/mediaelement/src/js/me-i18n-locale-zh-cn.js b/js/mediaelement/src/js/me-i18n-locale-zh-cn.js new file mode 100644 index 0000000000000000000000000000000000000000..561b9a3e28c0d28bb74aa761bec8f881b979705a --- /dev/null +++ b/js/mediaelement/src/js/me-i18n-locale-zh-cn.js @@ -0,0 +1,34 @@ +/* + * This is a i18n.locale language object. + * + * <zh-CN> Simplified Chinese translation by Michael J. Tong, + * michaeljayt@gmail.com + * + * @author + * Michael J. Tong (michaeljayt@gmail.com) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + exports['zh-CN'] = { + "Fullscreen" : "全屏", + "Go Fullscreen" : "全屏模式", + "Turn off Fullscreen" : "退出全屏模式", + "Close" : "关闭", + "Download File" : "下载文件", + "Play/Pause": "播放/暂停", + "Mute Toggle": "打开/取消静音", + "Captions/Subtitles": "字幕", + "None": "无", + "Unmute": "取消静音", + "Mute": "打开静音" + }; + +}(mejs.i18n.locale.strings)); diff --git a/js/mediaelement/src/js/me-i18n-locale-zh.js b/js/mediaelement/src/js/me-i18n-locale-zh.js new file mode 100644 index 0000000000000000000000000000000000000000..29138ef50d4d70d7d482d1fcddf6dcb61f565684 --- /dev/null +++ b/js/mediaelement/src/js/me-i18n-locale-zh.js @@ -0,0 +1,28 @@ +/* + * This is a i18n.locale language object. + * + * Traditional chinese translation by Tim Latz, latz.tim@gmail.com + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + if (typeof exports.zh === 'undefined') { + exports.zh = { + "Fullscreen" : "全螢幕", + "Go Fullscreen" : "全屏模式", + "Turn off Fullscreen" : "退出全屏模式", + "Close" : "關閉" + }; + } + +}(mejs.i18n.locale.strings)); diff --git a/js/mediaelement/src/js/me-i18n.js b/js/mediaelement/src/js/me-i18n.js new file mode 100644 index 0000000000000000000000000000000000000000..14866eb751b1858a63697bf3f606a4d612c66ee6 --- /dev/null +++ b/js/mediaelement/src/js/me-i18n.js @@ -0,0 +1,153 @@ +/* + * Adds Internationalization and localization to mediaelement. + * + * This file does not contain translations, you have to add the manually. + * The schema is always the same: me-i18n-locale-[ISO_639-1 Code].js + * + * Examples are provided both for german and chinese translation. + * + * + * What is the concept beyond i18n? + * http://en.wikipedia.org/wiki/Internationalization_and_localization + * + * What langcode should i use? + * http://en.wikipedia.org/wiki/ISO_639-1 + * + * + * License? + * + * The i18n file uses methods from the Drupal project (drupal.js): + * - i18n.methods.t() (modified) + * - i18n.methods.checkPlain() (full copy) + * + * The Drupal project is (like mediaelementjs) licensed under GPLv2. + * - http://drupal.org/licensing/faq/#q1 + * - https://github.com/johndyer/mediaelement + * - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * + * @params + * - context - document, iframe .. + * - exports - CommonJS, window .. + * + */ +;(function(context, exports, undefined) { + "use strict"; + var i18n = { + "locale": { + "language" : '', + "strings" : {} + }, + "methods" : {} + }; +// start i18n + + + /** + * Get language, fallback to browser's language if empty + */ + i18n.getLanguage = function () { + var language = i18n.locale.language || window.navigator.userLanguage || window.navigator.language; + // convert to iso 639-1 (2-letters, lower case) + return language.substr(0, 2).toLowerCase(); + }; + + // i18n fixes for compatibility with WordPress + if ( typeof mejsL10n != 'undefined' ) { + i18n.locale.language = mejsL10n.language; + } + + + + /** + * Encode special characters in a plain-text string for display as HTML. + */ + i18n.methods.checkPlain = function (str) { + var character, regex, + replace = { + '&': '&', + '"': '"', + '<': '<', + '>': '>' + }; + str = String(str); + for (character in replace) { + if (replace.hasOwnProperty(character)) { + regex = new RegExp(character, 'g'); + str = str.replace(regex, replace[character]); + } + } + return str; + }; + + /** + * Translate strings to the page language or a given language. + * + * + * @param str + * A string containing the English string to translate. + * + * @param options + * - 'context' (defaults to the default context): The context the source string + * belongs to. + * + * @return + * The translated string, escaped via i18n.methods.checkPlain() + */ + i18n.methods.t = function (str, options) { + + // Fetch the localized version of the string. + if (i18n.locale.strings && i18n.locale.strings[options.context] && i18n.locale.strings[options.context][str]) { + str = i18n.locale.strings[options.context][str]; + } + + return i18n.methods.checkPlain(str); + }; + + + /** + * Wrapper for i18n.methods.t() + * + * @see i18n.methods.t() + * @throws InvalidArgumentException + */ + i18n.t = function(str, options) { + + if (typeof str === 'string' && str.length > 0) { + + // check every time due language can change for + // different reasons (translation, lang switcher ..) + var language = i18n.getLanguage(); + + options = options || { + "context" : language + }; + + return i18n.methods.t(str, options); + } + else { + throw { + "name" : 'InvalidArgumentException', + "message" : 'First argument is either not a string or empty.' + }; + } + }; + +// end i18n + exports.i18n = i18n; +}(document, mejs)); + +// i18n fixes for compatibility with WordPress +;(function(exports, undefined) { + + "use strict"; + + if ( typeof mejsL10n != 'undefined' ) { + exports[mejsL10n.language] = mejsL10n.strings; + } + +}(mejs.i18n.locale.strings)); diff --git a/js/mediaelement/src/js/me-mediaelements.js b/js/mediaelement/src/js/me-mediaelements.js new file mode 100644 index 0000000000000000000000000000000000000000..e3fa086117c64dfb0dae70c8bb5fea3e3795a8df --- /dev/null +++ b/js/mediaelement/src/js/me-mediaelements.js @@ -0,0 +1,327 @@ +/* +extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below) +*/ +mejs.HtmlMediaElement = { + pluginType: 'native', + isFullScreen: false, + + setCurrentTime: function (time) { + this.currentTime = time; + }, + + setMuted: function (muted) { + this.muted = muted; + }, + + setVolume: function (volume) { + this.volume = volume; + }, + + // for parity with the plugin versions + stop: function () { + this.pause(); + }, + + // This can be a url string + // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] + setSrc: function (url) { + + // Fix for IE9 which can't set .src when there are <source> elements. Awesome, right? + var + existingSources = this.getElementsByTagName('source'); + while (existingSources.length > 0){ + this.removeChild(existingSources[0]); + } + + if (typeof url == 'string') { + this.src = url; + } else { + var i, media; + + for (i=0; i<url.length; i++) { + media = url[i]; + if (this.canPlayType(media.type)) { + this.src = media.src; + break; + } + } + } + }, + + setVideoSize: function (width, height) { + this.width = width; + this.height = height; + } +}; + +/* +Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember] +*/ +mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) { + this.id = pluginid; + this.pluginType = pluginType; + this.src = mediaUrl; + this.events = {}; + this.attributes = {}; +}; + +// JavaScript values and ExternalInterface methods that match HTML5 video properties methods +// http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html +// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html +mejs.PluginMediaElement.prototype = { + + // special + pluginElement: null, + pluginType: '', + isFullScreen: false, + + // not implemented :( + playbackRate: -1, + defaultPlaybackRate: -1, + seekable: [], + played: [], + + // HTML5 read-only properties + paused: true, + ended: false, + seeking: false, + duration: 0, + error: null, + tagName: '', + + // HTML5 get/set properties, but only set (updated by event handlers) + muted: false, + volume: 1, + currentTime: 0, + + // HTML5 methods + play: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.playVideo(); + } else { + this.pluginApi.playMedia(); + } + this.paused = false; + } + }, + load: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + } else { + this.pluginApi.loadMedia(); + } + + this.paused = false; + } + }, + pause: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.pauseVideo(); + } else { + this.pluginApi.pauseMedia(); + } + + + this.paused = true; + } + }, + stop: function () { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.stopVideo(); + } else { + this.pluginApi.stopMedia(); + } + this.paused = true; + } + }, + canPlayType: function(type) { + var i, + j, + pluginInfo, + pluginVersions = mejs.plugins[this.pluginType]; + + for (i=0; i<pluginVersions.length; i++) { + pluginInfo = pluginVersions[i]; + + // test if user has the correct plugin version + if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) { + + // test for plugin playback types + for (j=0; j<pluginInfo.types.length; j++) { + // find plugin that can play the type + if (type == pluginInfo.types[j]) { + return 'probably'; + } + } + } + } + + return ''; + }, + + positionFullscreenButton: function(x,y,visibleAndAbove) { + if (this.pluginApi != null && this.pluginApi.positionFullscreenButton) { + this.pluginApi.positionFullscreenButton(Math.floor(x),Math.floor(y),visibleAndAbove); + } + }, + + hideFullscreenButton: function() { + if (this.pluginApi != null && this.pluginApi.hideFullscreenButton) { + this.pluginApi.hideFullscreenButton(); + } + }, + + + // custom methods since not all JavaScript implementations support get/set + + // This can be a url string + // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] + setSrc: function (url) { + if (typeof url == 'string') { + this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url)); + this.src = mejs.Utility.absolutizeUrl(url); + } else { + var i, media; + + for (i=0; i<url.length; i++) { + media = url[i]; + if (this.canPlayType(media.type)) { + this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src)); + this.src = mejs.Utility.absolutizeUrl(url); + break; + } + } + } + + }, + setCurrentTime: function (time) { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube' || this.pluginType == 'vimeo') { + this.pluginApi.seekTo(time); + } else { + this.pluginApi.setCurrentTime(time); + } + + + + this.currentTime = time; + } + }, + setVolume: function (volume) { + if (this.pluginApi != null) { + // same on YouTube and MEjs + if (this.pluginType == 'youtube') { + this.pluginApi.setVolume(volume * 100); + } else { + this.pluginApi.setVolume(volume); + } + this.volume = volume; + } + }, + setMuted: function (muted) { + if (this.pluginApi != null) { + if (this.pluginType == 'youtube') { + if (muted) { + this.pluginApi.mute(); + } else { + this.pluginApi.unMute(); + } + this.muted = muted; + this.dispatchEvent('volumechange'); + } else { + this.pluginApi.setMuted(muted); + } + this.muted = muted; + } + }, + + // additional non-HTML5 methods + setVideoSize: function (width, height) { + + //if (this.pluginType == 'flash' || this.pluginType == 'silverlight') { + if (this.pluginElement && this.pluginElement.style) { + this.pluginElement.style.width = width + 'px'; + this.pluginElement.style.height = height + 'px'; + } + if (this.pluginApi != null && this.pluginApi.setVideoSize) { + this.pluginApi.setVideoSize(width, height); + } + //} + }, + + setFullscreen: function (fullscreen) { + if (this.pluginApi != null && this.pluginApi.setFullscreen) { + this.pluginApi.setFullscreen(fullscreen); + } + }, + + enterFullScreen: function() { + if (this.pluginApi != null && this.pluginApi.setFullscreen) { + this.setFullscreen(true); + } + + }, + + exitFullScreen: function() { + if (this.pluginApi != null && this.pluginApi.setFullscreen) { + this.setFullscreen(false); + } + }, + + // start: fake events + addEventListener: function (eventName, callback, bubble) { + this.events[eventName] = this.events[eventName] || []; + this.events[eventName].push(callback); + }, + removeEventListener: function (eventName, callback) { + if (!eventName) { this.events = {}; return true; } + var callbacks = this.events[eventName]; + if (!callbacks) return true; + if (!callback) { this.events[eventName] = []; return true; } + for (var i = 0; i < callbacks.length; i++) { + if (callbacks[i] === callback) { + this.events[eventName].splice(i, 1); + return true; + } + } + return false; + }, + dispatchEvent: function (eventName) { + var i, + args, + callbacks = this.events[eventName]; + + if (callbacks) { + args = Array.prototype.slice.call(arguments, 1); + for (i = 0; i < callbacks.length; i++) { + callbacks[i].apply(this, args); + } + } + }, + // end: fake events + + // fake DOM attribute methods + hasAttribute: function(name){ + return (name in this.attributes); + }, + removeAttribute: function(name){ + delete this.attributes[name]; + }, + getAttribute: function(name){ + if (this.hasAttribute(name)) { + return this.attributes[name]; + } + return ''; + }, + setAttribute: function(name, value){ + this.attributes[name] = value; + }, + + remove: function() { + mejs.Utility.removeSwf(this.pluginElement.id); + mejs.MediaPluginBridge.unregisterPluginElement(this.pluginElement.id); + } +}; diff --git a/js/mediaelement/src/js/me-namespace.js b/js/mediaelement/src/js/me-namespace.js new file mode 100644 index 0000000000000000000000000000000000000000..a421446265860fb54198efe85ec997fec08bc82f --- /dev/null +++ b/js/mediaelement/src/js/me-namespace.js @@ -0,0 +1,26 @@ +// Namespace +var mejs = mejs || {}; + +// version number +mejs.version = '2.15.1'; +console.log('ME.js version', mejs.version); + +// player number (for missing, same id attr) +mejs.meIndex = 0; + +// media types accepted by plugins +mejs.plugins = { + silverlight: [ + {version: [3,0], types: ['video/mp4','video/m4v','video/mov','video/wmv','audio/wma','audio/m4a','audio/mp3','audio/wav','audio/mpeg']} + ], + flash: [ + {version: [9,0,124], types: ['video/mp4','video/m4v','video/mov','video/flv','video/rtmp','video/x-flv','audio/flv','audio/x-flv','audio/mp3','audio/m4a','audio/mpeg', 'video/youtube', 'video/x-youtube', 'application/x-mpegURL']} + //,{version: [12,0], types: ['video/webm']} // for future reference (hopefully!) + ], + youtube: [ + {version: null, types: ['video/youtube', 'video/x-youtube', 'audio/youtube', 'audio/x-youtube']} + ], + vimeo: [ + {version: null, types: ['video/vimeo', 'video/x-vimeo']} + ] +}; diff --git a/js/mediaelement/src/js/me-plugindetector.js b/js/mediaelement/src/js/me-plugindetector.js new file mode 100644 index 0000000000000000000000000000000000000000..e71495f85334498b14c994fcb7d3e3c00818551c --- /dev/null +++ b/js/mediaelement/src/js/me-plugindetector.js @@ -0,0 +1,102 @@ + +// Core detector, plugins are added below +mejs.PluginDetector = { + + // main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]); + hasPluginVersion: function(plugin, v) { + var pv = this.plugins[plugin]; + v[1] = v[1] || 0; + v[2] = v[2] || 0; + return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; + }, + + // cached values + nav: window.navigator, + ua: window.navigator.userAgent.toLowerCase(), + + // stored version numbers + plugins: [], + + // runs detectPlugin() and stores the version number + addPlugin: function(p, pluginName, mimeType, activeX, axDetect) { + this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect); + }, + + // get the version number from the mimetype (all but IE) or ActiveX (IE) + detectPlugin: function(pluginName, mimeType, activeX, axDetect) { + + var version = [0,0,0], + description, + i, + ax; + + // Firefox, Webkit, Opera + if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') { + description = this.nav.plugins[pluginName].description; + if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) { + version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.'); + for (i=0; i<version.length; i++) { + version[i] = parseInt(version[i].match(/\d+/), 10); + } + } + // Internet Explorer / ActiveX + } else if (typeof(window.ActiveXObject) != 'undefined') { + try { + ax = new ActiveXObject(activeX); + if (ax) { + version = axDetect(ax); + } + } + catch (e) { } + } + return version; + } +}; + +// Add Flash detection +mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) { + // adapted from SWFObject + var version = [], + d = ax.GetVariable("$version"); + if (d) { + d = d.split(" ")[1].split(","); + version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + return version; +}); + +// Add Silverlight detection +mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) { + // Silverlight cannot report its version number to IE + // but it does have a isVersionSupported function, so we have to loop through it to get a version number. + // adapted from http://www.silverlightversion.com/ + var v = [0,0,0,0], + loopMatch = function(ax, v, i, n) { + while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){ + v[i]+=n; + } + v[i] -= n; + }; + loopMatch(ax, v, 0, 1); + loopMatch(ax, v, 1, 1); + loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx) + loopMatch(ax, v, 2, 1000); + loopMatch(ax, v, 2, 100); + loopMatch(ax, v, 2, 10); + loopMatch(ax, v, 2, 1); + loopMatch(ax, v, 3, 1); + + return v; +}); +// add adobe acrobat +/* +PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) { + var version = [], + d = ax.GetVersions().split(',')[0].split('=')[1].split('.'); + + if (d) { + version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + return version; +}); +*/ \ No newline at end of file diff --git a/js/mediaelement/src/js/me-shim.js b/js/mediaelement/src/js/me-shim.js new file mode 100644 index 0000000000000000000000000000000000000000..56510f65de4c223810175357c3d64752414e11e5 --- /dev/null +++ b/js/mediaelement/src/js/me-shim.js @@ -0,0 +1,982 @@ +// Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties +mejs.MediaPluginBridge = { + + pluginMediaElements:{}, + htmlMediaElements:{}, + + registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) { + this.pluginMediaElements[id] = pluginMediaElement; + this.htmlMediaElements[id] = htmlMediaElement; + }, + + unregisterPluginElement: function (id) { + delete this.pluginMediaElements[id]; + delete this.htmlMediaElements[id]; + }, + + // when Flash/Silverlight is ready, it calls out to this method + initPlugin: function (id) { + + var pluginMediaElement = this.pluginMediaElements[id], + htmlMediaElement = this.htmlMediaElements[id]; + + if (pluginMediaElement) { + // find the javascript bridge + switch (pluginMediaElement.pluginType) { + case "flash": + pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id); + break; + case "silverlight": + pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id); + pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS; + break; + } + + if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) { + pluginMediaElement.success(pluginMediaElement, htmlMediaElement); + } + } + }, + + // receives events from Flash/Silverlight and sends them out as HTML5 media events + // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html + fireEvent: function (id, eventName, values) { + + var + e, + i, + bufferedTime, + pluginMediaElement = this.pluginMediaElements[id]; + + if(!pluginMediaElement){ + return; + } + + // fake event object to mimic real HTML media event. + e = { + type: eventName, + target: pluginMediaElement + }; + + // attach all values to element and event object + for (i in values) { + pluginMediaElement[i] = values[i]; + e[i] = values[i]; + } + + // fake the newer W3C buffered TimeRange (loaded and total have been removed) + bufferedTime = values.bufferedTime || 0; + + e.target.buffered = e.buffered = { + start: function(index) { + return 0; + }, + end: function (index) { + return bufferedTime; + }, + length: 1 + }; + + pluginMediaElement.dispatchEvent(e.type, e); + } +}; + +/* +Default options +*/ +mejs.MediaElementDefaults = { + // allows testing on HTML5, flash, silverlight + // auto: attempts to detect what the browser can do + // auto_plugin: prefer plugins and then attempt native HTML5 + // native: forces HTML5 playback + // shim: disallows HTML5, will attempt either Flash or Silverlight + // none: forces fallback view + mode: 'auto', + // remove or reorder to change plugin priority and availability + plugins: ['flash','silverlight','youtube','vimeo'], + // shows debug errors on screen + enablePluginDebug: false, + // use plugin for browsers that have trouble with Basic Authentication on HTTPS sites + httpsBasicAuthSite: false, + // overrides the type specified, useful for dynamic instantiation + type: '', + // path to Flash and Silverlight plugins + pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']), + // name of flash file + flashName: 'flashmediaelement.swf', + // streamer for RTMP streaming + flashStreamer: '', + // turns on the smoothing filter in Flash + enablePluginSmoothing: false, + // enabled pseudo-streaming (seek) on .mp4 files + enablePseudoStreaming: false, + // start query parameter sent to server for pseudo-streaming + pseudoStreamingStartQueryParam: 'start', + // name of silverlight file + silverlightName: 'silverlightmediaelement.xap', + // default if the <video width> is not specified + defaultVideoWidth: 480, + // default if the <video height> is not specified + defaultVideoHeight: 270, + // overrides <video width> + pluginWidth: -1, + // overrides <video height> + pluginHeight: -1, + // additional plugin variables in 'key=value' form + pluginVars: [], + // rate in milliseconds for Flash and Silverlight to fire the timeupdate event + // larger number is less accurate, but less strain on plugin->JavaScript bridge + timerRate: 250, + // initial volume for player + startVolume: 0.8, + success: function () { }, + error: function () { } +}; + +/* +Determines if a browser supports the <video> or <audio> element +and returns either the native element or a Flash/Silverlight version that +mimics HTML5 MediaElement +*/ +mejs.MediaElement = function (el, o) { + return mejs.HtmlMediaElementShim.create(el,o); +}; + +mejs.HtmlMediaElementShim = { + + create: function(el, o) { + var + options = mejs.MediaElementDefaults, + htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el, + tagName = htmlMediaElement.tagName.toLowerCase(), + isMediaTag = (tagName === 'audio' || tagName === 'video'), + src = (isMediaTag) ? htmlMediaElement.getAttribute('src') : htmlMediaElement.getAttribute('href'), + poster = htmlMediaElement.getAttribute('poster'), + autoplay = htmlMediaElement.getAttribute('autoplay'), + preload = htmlMediaElement.getAttribute('preload'), + controls = htmlMediaElement.getAttribute('controls'), + playback, + prop; + + // extend options + for (prop in o) { + options[prop] = o[prop]; + } + + // clean up attributes + src = (typeof src == 'undefined' || src === null || src == '') ? null : src; + poster = (typeof poster == 'undefined' || poster === null) ? '' : poster; + preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload; + autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false'); + controls = !(typeof controls == 'undefined' || controls === null || controls === 'false'); + + // test for HTML5 and plugin capabilities + playback = this.determinePlayback(htmlMediaElement, options, mejs.MediaFeatures.supportsMediaTag, isMediaTag, src); + playback.url = (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : ''; + + if (playback.method == 'native') { + // second fix for android + if (mejs.MediaFeatures.isBustedAndroid) { + htmlMediaElement.src = playback.url; + htmlMediaElement.addEventListener('click', function() { + htmlMediaElement.play(); + }, false); + } + + // add methods to native HTMLMediaElement + return this.updateNative(playback, options, autoplay, preload); + } else if (playback.method !== '') { + // create plugin to mimic HTMLMediaElement + + return this.createPlugin( playback, options, poster, autoplay, preload, controls); + } else { + // boo, no HTML5, no Flash, no Silverlight. + this.createErrorMessage( playback, options, poster ); + + return this; + } + }, + + determinePlayback: function(htmlMediaElement, options, supportsMediaTag, isMediaTag, src) { + var + mediaFiles = [], + i, + j, + k, + l, + n, + type, + result = { method: '', url: '', htmlMediaElement: htmlMediaElement, isVideo: (htmlMediaElement.tagName.toLowerCase() != 'audio')}, + pluginName, + pluginVersions, + pluginInfo, + dummy, + media; + + // STEP 1: Get URL and type from <video src> or <source src> + + // supplied type overrides <video type> and <source type> + if (typeof options.type != 'undefined' && options.type !== '') { + + // accept either string or array of types + if (typeof options.type == 'string') { + mediaFiles.push({type:options.type, url:src}); + } else { + + for (i=0; i<options.type.length; i++) { + mediaFiles.push({type:options.type[i], url:src}); + } + } + + // test for src attribute first + } else if (src !== null) { + type = this.formatType(src, htmlMediaElement.getAttribute('type')); + mediaFiles.push({type:type, url:src}); + + // then test for <source> elements + } else { + // test <source> types to see if they are usable + for (i = 0; i < htmlMediaElement.childNodes.length; i++) { + n = htmlMediaElement.childNodes[i]; + if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') { + src = n.getAttribute('src'); + type = this.formatType(src, n.getAttribute('type')); + media = n.getAttribute('media'); + + if (!media || !window.matchMedia || (window.matchMedia && window.matchMedia(media).matches)) { + mediaFiles.push({type:type, url:src}); + } + } + } + } + + // in the case of dynamicly created players + // check for audio types + if (!isMediaTag && mediaFiles.length > 0 && mediaFiles[0].url !== null && this.getTypeFromFile(mediaFiles[0].url).indexOf('audio') > -1) { + result.isVideo = false; + } + + + // STEP 2: Test for playback method + + // special case for Android which sadly doesn't implement the canPlayType function (always returns '') + if (mejs.MediaFeatures.isBustedAndroid) { + htmlMediaElement.canPlayType = function(type) { + return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : ''; + }; + } + + // special case for Chromium to specify natively supported video codecs (i.e. WebM and Theora) + if (mejs.MediaFeatures.isChromium) { + htmlMediaElement.canPlayType = function(type) { + return (type.match(/video\/(webm|ogv|ogg)/gi) !== null) ? 'maybe' : ''; + }; + } + + // test for native playback first + if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'native') && !(mejs.MediaFeatures.isBustedNativeHTTPS && options.httpsBasicAuthSite === true)) { + + if (!isMediaTag) { + + // create a real HTML5 Media Element + dummy = document.createElement( result.isVideo ? 'video' : 'audio'); + htmlMediaElement.parentNode.insertBefore(dummy, htmlMediaElement); + htmlMediaElement.style.display = 'none'; + + // use this one from now on + result.htmlMediaElement = htmlMediaElement = dummy; + } + + for (i=0; i<mediaFiles.length; i++) { + // normal check + if (mediaFiles[i].type == "video/m3u8" || htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '' + // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg') + || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '' + // special case for m4a supported by detecting mp4 support + || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/m4a/,'mp4')).replace(/no/, '') !== '') { + result.method = 'native'; + result.url = mediaFiles[i].url; + break; + } + } + + if (result.method === 'native') { + if (result.url !== null) { + htmlMediaElement.src = result.url; + } + + // if `auto_plugin` mode, then cache the native result but try plugins. + if (options.mode !== 'auto_plugin') { + return result; + } + } + } + + // if native playback didn't work, then test plugins + if (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'shim') { + for (i=0; i<mediaFiles.length; i++) { + type = mediaFiles[i].type; + + // test all plugins in order of preference [silverlight, flash] + for (j=0; j<options.plugins.length; j++) { + + pluginName = options.plugins[j]; + + // test version of plugin (for future features) + pluginVersions = mejs.plugins[pluginName]; + + for (k=0; k<pluginVersions.length; k++) { + pluginInfo = pluginVersions[k]; + + // test if user has the correct plugin version + + // for youtube/vimeo + if (pluginInfo.version == null || + + mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) { + + // test for plugin playback types + for (l=0; l<pluginInfo.types.length; l++) { + // find plugin that can play the type + if (type == pluginInfo.types[l]) { + result.method = pluginName; + result.url = mediaFiles[i].url; + return result; + } + } + } + } + } + } + } + + // at this point, being in 'auto_plugin' mode implies that we tried plugins but failed. + // if we have native support then return that. + if (options.mode === 'auto_plugin' && result.method === 'native') { + return result; + } + + // what if there's nothing to play? just grab the first available + if (result.method === '' && mediaFiles.length > 0) { + result.url = mediaFiles[0].url; + } + + return result; + }, + + formatType: function(url, type) { + var ext; + + // if no type is supplied, fake it with the extension + if (url && !type) { + return this.getTypeFromFile(url); + } else { + // only return the mime part of the type in case the attribute contains the codec + // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element + // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4` + + if (type && ~type.indexOf(';')) { + return type.substr(0, type.indexOf(';')); + } else { + return type; + } + } + }, + + getTypeFromFile: function(url) { + url = url.split('?')[0]; + var ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase(); + return (/(mp4|m4v|ogg|ogv|m3u8|webm|webmv|flv|wmv|mpeg|mov)/gi.test(ext) ? 'video' : 'audio') + '/' + this.getTypeFromExtension(ext); + }, + + getTypeFromExtension: function(ext) { + + switch (ext) { + case 'mp4': + case 'm4v': + case 'm4a': + return 'mp4'; + case 'webm': + case 'webma': + case 'webmv': + return 'webm'; + case 'ogg': + case 'oga': + case 'ogv': + return 'ogg'; + default: + return ext; + } + }, + + createErrorMessage: function(playback, options, poster) { + var + htmlMediaElement = playback.htmlMediaElement, + errorContainer = document.createElement('div'); + + errorContainer.className = 'me-cannotplay'; + + try { + errorContainer.style.width = htmlMediaElement.width + 'px'; + errorContainer.style.height = htmlMediaElement.height + 'px'; + } catch (e) {} + + if (options.customError) { + errorContainer.innerHTML = options.customError; + } else { + errorContainer.innerHTML = (poster !== '') ? + '<a href="' + playback.url + '"><img src="' + poster + '" width="100%" height="100%" /></a>' : + '<a href="' + playback.url + '"><span>' + mejs.i18n.t('Download File') + '</span></a>'; + } + + htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement); + htmlMediaElement.style.display = 'none'; + + options.error(htmlMediaElement); + }, + + createPlugin:function(playback, options, poster, autoplay, preload, controls) { + var + htmlMediaElement = playback.htmlMediaElement, + width = 1, + height = 1, + pluginid = 'me_' + playback.method + '_' + (mejs.meIndex++), + pluginMediaElement = new mejs.PluginMediaElement(pluginid, playback.method, playback.url), + container = document.createElement('div'), + specialIEContainer, + node, + initVars; + + // copy tagName from html media element + pluginMediaElement.tagName = htmlMediaElement.tagName + + // copy attributes from html media element to plugin media element + for (var i = 0; i < htmlMediaElement.attributes.length; i++) { + var attribute = htmlMediaElement.attributes[i]; + if (attribute.specified == true) { + pluginMediaElement.setAttribute(attribute.name, attribute.value); + } + } + + // check for placement inside a <p> tag (sometimes WYSIWYG editors do this) + node = htmlMediaElement.parentNode; + while (node !== null && node.tagName.toLowerCase() !== 'body' && node.parentNode != null) { + if (node.parentNode.tagName.toLowerCase() === 'p') { + node.parentNode.parentNode.insertBefore(node, node.parentNode); + break; + } + node = node.parentNode; + } + + if (playback.isVideo) { + width = (options.pluginWidth > 0) ? options.pluginWidth : (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth; + height = (options.pluginHeight > 0) ? options.pluginHeight : (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight; + + // in case of '%' make sure it's encoded + width = mejs.Utility.encodeUrl(width); + height = mejs.Utility.encodeUrl(height); + + } else { + if (options.enablePluginDebug) { + width = 320; + height = 240; + } + } + + // register plugin + pluginMediaElement.success = options.success; + mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement); + + // add container (must be added to DOM before inserting HTML for IE) + container.className = 'me-plugin'; + container.id = pluginid + '_container'; + + if (playback.isVideo) { + htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement); + } else { + document.body.insertBefore(container, document.body.childNodes[0]); + } + + // flash/silverlight vars + initVars = [ + 'id=' + pluginid, + 'isvideo=' + ((playback.isVideo) ? "true" : "false"), + 'autoplay=' + ((autoplay) ? "true" : "false"), + 'preload=' + preload, + 'width=' + width, + 'startvolume=' + options.startVolume, + 'timerrate=' + options.timerRate, + 'flashstreamer=' + options.flashStreamer, + 'height=' + height, + 'pseudostreamstart=' + options.pseudoStreamingStartQueryParam]; + + if (playback.url !== null) { + if (playback.method == 'flash') { + initVars.push('file=' + mejs.Utility.encodeUrl(playback.url)); + } else { + initVars.push('file=' + playback.url); + } + } + if (options.enablePluginDebug) { + initVars.push('debug=true'); + } + if (options.enablePluginSmoothing) { + initVars.push('smoothing=true'); + } + if (options.enablePseudoStreaming) { + initVars.push('pseudostreaming=true'); + } + if (controls) { + initVars.push('controls=true'); // shows controls in the plugin if desired + } + if (options.pluginVars) { + initVars = initVars.concat(options.pluginVars); + } + + switch (playback.method) { + case 'silverlight': + container.innerHTML = +'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' + +'<param name="initParams" value="' + initVars.join(',') + '" />' + +'<param name="windowless" value="true" />' + +'<param name="background" value="black" />' + +'<param name="minRuntimeVersion" value="3.0.0.0" />' + +'<param name="autoUpgrade" value="true" />' + +'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' + +'</object>'; + break; + + case 'flash': + + if (mejs.MediaFeatures.isIE) { + specialIEContainer = document.createElement('div'); + container.appendChild(specialIEContainer); + specialIEContainer.outerHTML = +'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + +'id="' + pluginid + '" width="' + width + '" height="' + height + '" class="mejs-shim">' + +'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' + +'<param name="flashvars" value="' + initVars.join('&') + '" />' + +'<param name="quality" value="high" />' + +'<param name="bgcolor" value="#000000" />' + +'<param name="wmode" value="transparent" />' + +'<param name="allowScriptAccess" value="always" />' + +'<param name="allowFullScreen" value="true" />' + +'<param name="scale" value="default" />' + +'</object>'; + + } else { + + container.innerHTML = +'<embed id="' + pluginid + '" name="' + pluginid + '" ' + +'play="true" ' + +'loop="false" ' + +'quality="high" ' + +'bgcolor="#000000" ' + +'wmode="transparent" ' + +'allowScriptAccess="always" ' + +'allowFullScreen="true" ' + +'type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" ' + +'src="' + options.pluginPath + options.flashName + '" ' + +'flashvars="' + initVars.join('&') + '" ' + +'width="' + width + '" ' + +'height="' + height + '" ' + +'scale="default"' + +'class="mejs-shim"></embed>'; + } + break; + + case 'youtube': + + + var videoId; + // youtu.be url from share button + if (playback.url.lastIndexOf("youtu.be") != -1) { + videoId = playback.url.substr(playback.url.lastIndexOf('/')+1); + if (videoId.indexOf('?') != -1) { + videoId = videoId.substr(0, videoId.indexOf('?')); + } + } + else { + videoId = playback.url.substr(playback.url.lastIndexOf('=')+1); + } + youtubeSettings = { + container: container, + containerId: container.id, + pluginMediaElement: pluginMediaElement, + pluginId: pluginid, + videoId: videoId, + height: height, + width: width + }; + + if (mejs.PluginDetector.hasPluginVersion('flash', [10,0,0]) ) { + mejs.YouTubeApi.createFlash(youtubeSettings); + } else { + mejs.YouTubeApi.enqueueIframe(youtubeSettings); + } + + break; + + // DEMO Code. Does NOT work. + case 'vimeo': + var player_id = pluginid + "_player"; + pluginMediaElement.vimeoid = playback.url.substr(playback.url.lastIndexOf('/')+1); + + container.innerHTML ='<iframe src="//player.vimeo.com/video/' + pluginMediaElement.vimeoid + '?api=1&portrait=0&byline=0&title=0&player_id=' + player_id + '" width="' + width +'" height="' + height +'" frameborder="0" class="mejs-shim" id="' + player_id + '" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'; + if (typeof($f) == 'function') { // froogaloop available + var player = $f(container.childNodes[0]); + player.addEvent('ready', function() { + $.extend( player, { + playVideo: function() { + player.api( 'play' ); + }, + stopVideo: function() { + player.api( 'unload' ); + }, + pauseVideo: function() { + player.api( 'pause' ); + }, + seekTo: function( seconds ) { + player.api( 'seekTo', seconds ); + }, + setVolume: function( volume ) { + player.api( 'setVolume', volume ); + }, + setMuted: function( muted ) { + if( muted ) { + player.lastVolume = player.api( 'getVolume' ); + player.api( 'setVolume', 0 ); + } else { + player.api( 'setVolume', player.lastVolume ); + delete player.lastVolume; + } + } + }); + + function createEvent(player, pluginMediaElement, eventName, e) { + var obj = { + type: eventName, + target: pluginMediaElement + }; + if (eventName == 'timeupdate') { + pluginMediaElement.currentTime = obj.currentTime = e.seconds; + pluginMediaElement.duration = obj.duration = e.duration; + } + pluginMediaElement.dispatchEvent(obj.type, obj); + } + + player.addEvent('play', function() { + createEvent(player, pluginMediaElement, 'play'); + createEvent(player, pluginMediaElement, 'playing'); + }); + + player.addEvent('pause', function() { + createEvent(player, pluginMediaElement, 'pause'); + }); + + player.addEvent('finish', function() { + createEvent(player, pluginMediaElement, 'ended'); + }); + + player.addEvent('playProgress', function(e) { + createEvent(player, pluginMediaElement, 'timeupdate', e); + }); + + pluginMediaElement.pluginElement = container; + pluginMediaElement.pluginApi = player; + + // init mejs + mejs.MediaPluginBridge.initPlugin(pluginid); + }); + } + else { + console.warn("You need to include froogaloop for vimeo to work"); + } + break; + } + // hide original element + htmlMediaElement.style.display = 'none'; + // prevent browser from autoplaying when using a plugin + htmlMediaElement.removeAttribute('autoplay'); + + // FYI: options.success will be fired by the MediaPluginBridge + + return pluginMediaElement; + }, + + updateNative: function(playback, options, autoplay, preload) { + + var htmlMediaElement = playback.htmlMediaElement, + m; + + + // add methods to video object to bring it into parity with Flash Object + for (m in mejs.HtmlMediaElement) { + htmlMediaElement[m] = mejs.HtmlMediaElement[m]; + } + + /* + Chrome now supports preload="none" + if (mejs.MediaFeatures.isChrome) { + + // special case to enforce preload attribute (Chrome doesn't respect this) + if (preload === 'none' && !autoplay) { + + // forces the browser to stop loading (note: fails in IE9) + htmlMediaElement.src = ''; + htmlMediaElement.load(); + htmlMediaElement.canceledPreload = true; + + htmlMediaElement.addEventListener('play',function() { + if (htmlMediaElement.canceledPreload) { + htmlMediaElement.src = playback.url; + htmlMediaElement.load(); + htmlMediaElement.play(); + htmlMediaElement.canceledPreload = false; + } + }, false); + // for some reason Chrome forgets how to autoplay sometimes. + } else if (autoplay) { + htmlMediaElement.load(); + htmlMediaElement.play(); + } + } + */ + + // fire success code + options.success(htmlMediaElement, htmlMediaElement); + + return htmlMediaElement; + } +}; + +/* + - test on IE (object vs. embed) + - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE) + - fullscreen? +*/ + +// YouTube Flash and Iframe API +mejs.YouTubeApi = { + isIframeStarted: false, + isIframeLoaded: false, + loadIframeApi: function() { + if (!this.isIframeStarted) { + var tag = document.createElement('script'); + tag.src = "//www.youtube.com/player_api"; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + this.isIframeStarted = true; + } + }, + iframeQueue: [], + enqueueIframe: function(yt) { + + if (this.isLoaded) { + this.createIframe(yt); + } else { + this.loadIframeApi(); + this.iframeQueue.push(yt); + } + }, + createIframe: function(settings) { + + var + pluginMediaElement = settings.pluginMediaElement, + player = new YT.Player(settings.containerId, { + height: settings.height, + width: settings.width, + videoId: settings.videoId, + playerVars: {controls:0}, + events: { + 'onReady': function() { + + // hook up iframe object to MEjs + settings.pluginMediaElement.pluginApi = player; + + // init mejs + mejs.MediaPluginBridge.initPlugin(settings.pluginId); + + // create timer + setInterval(function() { + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); + }, 250); + }, + 'onStateChange': function(e) { + + mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement); + + } + } + }); + }, + + createEvent: function (player, pluginMediaElement, eventName) { + var obj = { + type: eventName, + target: pluginMediaElement + }; + + if (player && player.getDuration) { + + // time + pluginMediaElement.currentTime = obj.currentTime = player.getCurrentTime(); + pluginMediaElement.duration = obj.duration = player.getDuration(); + + // state + obj.paused = pluginMediaElement.paused; + obj.ended = pluginMediaElement.ended; + + // sound + obj.muted = player.isMuted(); + obj.volume = player.getVolume() / 100; + + // progress + obj.bytesTotal = player.getVideoBytesTotal(); + obj.bufferedBytes = player.getVideoBytesLoaded(); + + // fake the W3C buffered TimeRange + var bufferedTime = obj.bufferedBytes / obj.bytesTotal * obj.duration; + + obj.target.buffered = obj.buffered = { + start: function(index) { + return 0; + }, + end: function (index) { + return bufferedTime; + }, + length: 1 + }; + + } + + // send event up the chain + pluginMediaElement.dispatchEvent(obj.type, obj); + }, + + iFrameReady: function() { + + this.isLoaded = true; + this.isIframeLoaded = true; + + while (this.iframeQueue.length > 0) { + var settings = this.iframeQueue.pop(); + this.createIframe(settings); + } + }, + + // FLASH! + flashPlayers: {}, + createFlash: function(settings) { + + this.flashPlayers[settings.pluginId] = settings; + + /* + settings.container.innerHTML = + '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0" ' + + 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' + + '<param name="allowScriptAccess" value="always">' + + '<param name="wmode" value="transparent">' + + '</object>'; + */ + + var specialIEContainer, + youtubeUrl = '//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0'; + + if (mejs.MediaFeatures.isIE) { + + specialIEContainer = document.createElement('div'); + settings.container.appendChild(specialIEContainer); + specialIEContainer.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + +'id="' + settings.pluginId + '" width="' + settings.width + '" height="' + settings.height + '" class="mejs-shim">' + + '<param name="movie" value="' + youtubeUrl + '" />' + + '<param name="wmode" value="transparent" />' + + '<param name="allowScriptAccess" value="always" />' + + '<param name="allowFullScreen" value="true" />' + +'</object>'; + } else { + settings.container.innerHTML = + '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="' + youtubeUrl + '" ' + + 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; " class="mejs-shim">' + + '<param name="allowScriptAccess" value="always">' + + '<param name="wmode" value="transparent">' + + '</object>'; + } + + }, + + flashReady: function(id) { + var + settings = this.flashPlayers[id], + player = document.getElementById(id), + pluginMediaElement = settings.pluginMediaElement; + + // hook up and return to MediaELementPlayer.success + pluginMediaElement.pluginApi = + pluginMediaElement.pluginElement = player; + mejs.MediaPluginBridge.initPlugin(id); + + // load the youtube video + player.cueVideoById(settings.videoId); + + var callbackName = settings.containerId + '_callback'; + + window[callbackName] = function(e) { + mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement); + } + + player.addEventListener('onStateChange', callbackName); + + setInterval(function() { + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); + }, 250); + + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'canplay'); + }, + + handleStateChange: function(youTubeState, player, pluginMediaElement) { + switch (youTubeState) { + case -1: // not started + pluginMediaElement.paused = true; + pluginMediaElement.ended = true; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata'); + //createYouTubeEvent(player, pluginMediaElement, 'loadeddata'); + break; + case 0: + pluginMediaElement.paused = false; + pluginMediaElement.ended = true; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended'); + break; + case 1: + pluginMediaElement.paused = false; + pluginMediaElement.ended = false; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play'); + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing'); + break; + case 2: + pluginMediaElement.paused = true; + pluginMediaElement.ended = false; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause'); + break; + case 3: // buffering + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress'); + break; + case 5: + // cued? + break; + + } + + } +} +// IFRAME +function onYouTubePlayerAPIReady() { + mejs.YouTubeApi.iFrameReady(); +} +// FLASH +function onYouTubePlayerReady(id) { + mejs.YouTubeApi.flashReady(id); +} + +window.mejs = mejs; +window.MediaElement = mejs.MediaElement; diff --git a/js/mediaelement/src/js/me-utility.js b/js/mediaelement/src/js/me-utility.js new file mode 100644 index 0000000000000000000000000000000000000000..305cf3e5521a8863aec4691725d363680eb2c7d7 --- /dev/null +++ b/js/mediaelement/src/js/me-utility.js @@ -0,0 +1,158 @@ +/* +Utility methods +*/ +mejs.Utility = { + encodeUrl: function(url) { + return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26'); + }, + escapeHTML: function(s) { + return s.toString().split('&').join('&').split('<').join('<').split('"').join('"'); + }, + absolutizeUrl: function(url) { + var el = document.createElement('div'); + el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>'; + return el.firstChild.href; + }, + getScriptPath: function(scriptNames) { + var + i = 0, + j, + codePath = '', + testname = '', + slashPos, + filenamePos, + scriptUrl, + scriptPath, + scriptFilename, + scripts = document.getElementsByTagName('script'), + il = scripts.length, + jl = scriptNames.length; + + // go through all <script> tags + for (; i < il; i++) { + scriptUrl = scripts[i].src; + slashPos = scriptUrl.lastIndexOf('/'); + if (slashPos > -1) { + scriptFilename = scriptUrl.substring(slashPos + 1); + scriptPath = scriptUrl.substring(0, slashPos + 1); + } else { + scriptFilename = scriptUrl; + scriptPath = ''; + } + + // see if any <script> tags have a file name that matches the + for (j = 0; j < jl; j++) { + testname = scriptNames[j]; + filenamePos = scriptFilename.indexOf(testname); + if (filenamePos > -1) { + codePath = scriptPath; + break; + } + } + + // if we found a path, then break and return it + if (codePath !== '') { + break; + } + } + + // send the best path back + return codePath; + }, + secondsToTimeCode: function(time, forceHours, showFrameCount, fps) { + //add framecount + if (typeof showFrameCount == 'undefined') { + showFrameCount=false; + } else if(typeof fps == 'undefined') { + fps = 25; + } + + var hours = Math.floor(time / 3600) % 24, + minutes = Math.floor(time / 60) % 60, + seconds = Math.floor(time % 60), + frames = Math.floor(((time % 1)*fps).toFixed(3)), + result = + ( (forceHours || hours > 0) ? (hours < 10 ? '0' + hours : hours) + ':' : '') + + (minutes < 10 ? '0' + minutes : minutes) + ':' + + (seconds < 10 ? '0' + seconds : seconds) + + ((showFrameCount) ? ':' + (frames < 10 ? '0' + frames : frames) : ''); + + return result; + }, + + timeCodeToSeconds: function(hh_mm_ss_ff, forceHours, showFrameCount, fps){ + if (typeof showFrameCount == 'undefined') { + showFrameCount=false; + } else if(typeof fps == 'undefined') { + fps = 25; + } + + var tc_array = hh_mm_ss_ff.split(":"), + tc_hh = parseInt(tc_array[0], 10), + tc_mm = parseInt(tc_array[1], 10), + tc_ss = parseInt(tc_array[2], 10), + tc_ff = 0, + tc_in_seconds = 0; + + if (showFrameCount) { + tc_ff = parseInt(tc_array[3])/fps; + } + + tc_in_seconds = ( tc_hh * 3600 ) + ( tc_mm * 60 ) + tc_ss + tc_ff; + + return tc_in_seconds; + }, + + + convertSMPTEtoSeconds: function (SMPTE) { + if (typeof SMPTE != 'string') + return false; + + SMPTE = SMPTE.replace(',', '.'); + + var secs = 0, + decimalLen = (SMPTE.indexOf('.') != -1) ? SMPTE.split('.')[1].length : 0, + multiplier = 1; + + SMPTE = SMPTE.split(':').reverse(); + + for (var i = 0; i < SMPTE.length; i++) { + multiplier = 1; + if (i > 0) { + multiplier = Math.pow(60, i); + } + secs += Number(SMPTE[i]) * multiplier; + } + return Number(secs.toFixed(decimalLen)); + }, + + /* borrowed from SWFObject: http://code.google.com/p/swfobject/source/browse/trunk/swfobject/src/swfobject.js#474 */ + removeSwf: function(id) { + var obj = document.getElementById(id); + if (obj && /object|embed/i.test(obj.nodeName)) { + if (mejs.MediaFeatures.isIE) { + obj.style.display = "none"; + (function(){ + if (obj.readyState == 4) { + mejs.Utility.removeObjectInIE(id); + } else { + setTimeout(arguments.callee, 10); + } + })(); + } else { + obj.parentNode.removeChild(obj); + } + } + }, + removeObjectInIE: function(id) { + var obj = document.getElementById(id); + if (obj) { + for (var i in obj) { + if (typeof obj[i] == "function") { + obj[i] = null; + } + } + obj.parentNode.removeChild(obj); + } + } +}; diff --git a/js/mediaelement/src/js/mep-feature-ads-vast.js b/js/mediaelement/src/js/mep-feature-ads-vast.js new file mode 100644 index 0000000000000000000000000000000000000000..623419dc26c81bdcf64f229e47e16f9fb7c75ea1 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-ads-vast.js @@ -0,0 +1,243 @@ + +// VAST ads plugin +// Sponsored by Minoto Video + +(function($) { + + $.extend(mejs.MepDefaults, { + // URL to vast data: 'http://minotovideo.com/sites/minotovideo.com/files/upload/eday_vast_tag.xml' + vastAdTagUrl: '' + }); + + $.extend(MediaElementPlayer.prototype, { + buildvast: function(player, controls, layers, media) { + + var t = this; + + // begin loading + if (t.options.vastAdTagUrl != '') { + t.vastLoadAdTagInfo(); + } + + // make sure the preroll ad system is ready (it will ensure it can't be called twice) + t.buildads(player, controls, layers, media); + + + t.vastSetupEvents(); + }, + + vastAdTagIsLoading: false, + + vastAdTagIsLoaded: false, + + vastStartedPlaying: false, + + vastAdTags: [], + + vastSetupEvents: function() { + var t = this; + + + // START: preroll + t.container.on('mejsprerollstarted', function() { + + console.log('VAST','mejsprerollstarted'); + + if (t.vastAdTags.length > 0) { + + var adTag = t.vastAdTags[0]; + + // always fire this event + if (adTag.trackingEvents['start']) { + t.adsLoadUrl(adTag.trackingEvents['start']); + } + + // only do impressions once + if (!adTag.shown && adTag.impressions.length > 0) { + + for (var i=0, il=adTag.impressions.length; i<il; i++) { + t.adsLoadUrl(adTag.impressions[i]); + } + } + + adTag.shown = true; + } + + }); + + // END: preroll + t.container.on('mejsprerollended', function() { + + console.log('VAST','mejsprerollended'); + + if (t.vastAdTags.length > 0 && t.vastAdTags[0].trackingEvents['complete']) { + t.adsLoadUrl(t.vastAdTags[0].trackingEvents['complete']); + } + + }); + + }, + + vastSetAdTagUrl: function(url) { + + var t = this; + + // set and reset + t.options.vastAdTagUrl = url; + t.vastAdTagIsLoaded = false; + t.vastAdTags = []; + }, + + vastLoadAdTagInfo: function() { + console.log('loading vast ad data'); + + var t = this; + + // set this to stop playback + t.adsDataIsLoading = true; + t.vastAdTagIsLoading = true; + + // try straight load first + t.loadAdTagInfoDirect(); + }, + + loadAdTagInfoDirect: function() { + console.log('loading vast:direct'); + + var t= this; + + $.ajax({ + url: t.options.vastAdTagUrl, + crossDomain: true, + success: function(data) { + console.log('vast:direct:success', data); + + t.vastParseVastData(data) + }, + error: function(err) { + console.log('vast:direct:error', err); + + // fallback to Yahoo proxy + t.loadAdTagInfoProxy(); + } + }); + }, + + loadAdTagInfoProxy: function() { + console.log('loading vast:proxy:yahoo'); + + var t = this, + protocol = location.protocol, + hostname = location.hostname, + exRegex = RegExp(protocol + '//' + hostname), + query = 'select * from xml where url="' + encodeURI(t.options.vastAdTagUrl) +'"', + yahooUrl = 'http' + (/^https/.test(protocol)?'s':'') + '://query.yahooapis.com/v1/public/yql?format=xml&q=' + query; + + + + $.ajax({ + url: yahooUrl, + crossDomain: true, + success: function(data) { + console.log('vast:proxy:yahoo:success', data); + + t.vastParseVastData(data); + }, + error: function(err) { + console.log('vast:proxy:yahoo:error', err); + } + }); + }, + + vastParseVastData: function(data) { + + var t = this; + + // clear out data + t.vastAdTags = []; + + $(data).find('Ad').each(function(index, node) { + + var + adNode = $(node), + + adTag = { + id: adNode.attr('id'), + title: $.trim( adNode.find('AdTitle').text() ), + description: $.trim( adNode.find('Description').text() ), + impressions: [], + clickThrough: $.trim( adNode.find('ClickThrough').text() ), + mediaFiles: [], + trackingEvents: {}, + + // internal tracking if it's been used + shown: false + }; + + t.vastAdTags.push(adTag); + + + // parse all needed nodes + adNode.find('Impression').each(function() { + adTag.impressions.push( $.trim( $(this).text() ) ); + }); + + adNode.find('Tracking').each(function(index, node) { + var trackingEvent = $(node); + + adTag.trackingEvents[trackingEvent.attr('event')] = $.trim( trackingEvent.text() ); + + }); + + + adNode.find('MediaFile').each(function(index, node) { + var mediaFile = $(node), + type = mediaFile.attr('type'); + + if (t.media.canPlayType(type).toString().replace(/no/,'').replace(/false/,'') != '') { + + adTag.mediaFiles.push({ + id: mediaFile.attr('id'), + delivery: mediaFile.attr('delivery'), + type: mediaFile.attr('type'), + bitrate: mediaFile.attr('bitrate'), + width: mediaFile.attr('width'), + height: mediaFile.attr('height'), + url: $.trim( mediaFile.text() ) + } ); + } + }); + + }); + + // DONE + t.vastLoaded(); + }, + + vastLoaded: function() { + var t = this; + + t.vastAdTagIsLoaded = true; + t.vastAdTagIsLoading = false; + t.adsDataIsLoading = false; + + t.vastStartPreroll(); + }, + + vastStartPreroll: function() { + console.log('vastStartPreroll'); + + var t = this; + + // if we have a media URL, then send it up to the ads plugin as a preroll + if (t.vastAdTags.length > 0 && t.vastAdTags[0].mediaFiles.length > 0) { + + t.options.adsPrerollMediaUrl = t.vastAdTags[0].mediaFiles[0].url; + t.options.adsPrerollAdUrl = t.vastAdTags[0].clickThrough; + t.adsStartPreroll(); + } + } + + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-ads.js b/js/mediaelement/src/js/mep-feature-ads.js new file mode 100644 index 0000000000000000000000000000000000000000..636de7bc3e39fef0895fb799a0630e571692bc14 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-ads.js @@ -0,0 +1,312 @@ +// VAST ads plugin +// Sponsored by Minoto Video + +// 2013/02/01 0.5 research +// 2013/02/09 1.5 build loading mechanism +// 2013/02/10 2.5 events to play preroll, skip function, start/end calls, \ +// 2013/02/11 2 click events +// ---- +// 2013/02/23 3.5 split into a generic pre-roll plugin + + +(function($) { + + // on time insert into head + $('head').append($('<style>' + +'.mejs-ads a {' + +' display: block; ' + +' position: absolute;' + +' right: 0;' + +' top: 0;' + +' width: 100%; ' + +' height: 100%; ' + +' display: block; ' + +'}' + +'.mejs-ads .mejs-ads-skip-block {' + +' display: block; ' + +' position: absolute;' + +' right: 0;' + +' top: 0;' + +' padding: 10px; ' + +' background: #000; ' + +' background: rgba(0,0,0,0.5); ' + +' color: #fff; ' + +'}' + +'.mejs-ads .mejs-ads-skip-button {' + +' cursor: pointer; ' + +'}' + +'.mejs-ads .mejs-ads-skip-button:hover {' + +' text-decoration: underline; ' + +'}' + + '</style>')); + + + $.extend(mejs.MepDefaults, { + // URL to a media file + adsPrerollMediaUrl: '', + + // URL for lcicking ad + adsPrerollAdUrl: '', + + // if true, allows user to skip the pre-roll ad + adsPrerollAdEnableSkip: false, + + // if adsPrerollAdEnableSkip=true and this is a positive number, it will only allow skipping after the time has elasped + adsPrerollAdSkipSeconds: -1 + }); + + $.extend(MediaElementPlayer.prototype, { + + // allows other plugins to all this one + adsLoaded: false, + + // prevents playback in until async ad data is ready (e.g. VAST) + adsDataIsLoading: false, + + // stores the main media URL when an ad is playing + adsCurrentMediaUrl: '', + adsCurrentMediaDuration: 0, + + // true when the user clicks play for the first time, or if autoplay is set + adsPlayerHasStarted: false, + + buildads: function(player, controls, layers, media) { + + var t = this; + + if (t.adsLoaded) { + return; + } else { + t.adsLoaded = true; + } + + // add layer for ad links and skipping + player.adsLayer = + $('<div class="mejs-layer mejs-overlay mejs-ads">' + + '<a href="#" target="_blank"> </a>' + + '<div class="mejs-ads-skip-block">' + + '<span class="mejs-ads-skip-message"></span>' + + '<span class="mejs-ads-skip-button">Skip Ad »</span>' + + '</div>' + + '</div>') + .insertBefore( layers.find('.mejs-overlay-play') ) + .hide(); + + player.adsLayer.find('a') + .on('click', $.proxy(t.adsAdClick, t) ); + + player.adsSkipBlock = player.adsLayer.find('.mejs-ads-skip-block').hide(); + player.adsSkipMessage = player.adsLayer.find('.mejs-ads-skip-message').hide(); + + player.adsSkipButton = player.adsLayer.find('.mejs-ads-skip-button') + .on('click', $.proxy(t.adsSkipClick, t) ); + + + // create proxies (only needed for events we want to remove later) + t.adsMediaTryingToStartProxy = $.proxy(t.adsMediaTryingToStart, t); + t.adsPrerollStartedProxy = $.proxy(t.adsPrerollStarted, t); + t.adsPrerollMetaProxy = $.proxy(t.adsPrerollMeta, t); + t.adsPrerollUpdateProxy = $.proxy(t.adsPrerollUpdate, t); + t.adsPrerollEndedProxy = $.proxy(t.adsPrerollEnded, t); + + // check for start + t.media.addEventListener('play', t.adsMediaTryingToStartProxy ); + t.media.addEventListener('playing', t.adsMediaTryingToStartProxy ); + t.media.addEventListener('canplay', t.adsMediaTryingToStartProxy ); + t.media.addEventListener('loadedmetadata', t.adsMediaTryingToStartProxy ); + + console.log('setup ads', t.options.adsPrerollMediaUrl); + + if (t.options.adsPrerollMediaUrl != '') { + t.adsStartPreroll(); + } + }, + + + adsMediaTryingToStart: function() { + + var t = this; + + // make sure to pause until the ad data is loaded + if (t.adsDataIsLoading && !t.media.paused) { + t.media.pause(); + } + + t.adsPlayerHasStarted = true; + }, + + adsStartPreroll: function() { + + var t = this; + + console.log('adsStartPreroll', 'url', t.options.adsPrerollMediaUrl); + + + t.media.addEventListener('loadedmetadata', t.adsPrerollMetaProxy ); + t.media.addEventListener('playing', t.adsPrerollStartedProxy ); + t.media.addEventListener('ended', t.adsPrerollEndedProxy ) + t.media.addEventListener('timeupdate', t.adsPrerollUpdateProxy ); + + // change URLs to the preroll ad + t.adsCurrentMediaUrl = t.media.src; + t.adsCurrentMediaDuration = t.media.duration; + + t.media.setSrc(t.options.adsPrerollMediaUrl); + t.media.load(); + + // if autoplay was on, or if the user pressed play + // while the ad data was still loading, then start the ad right away + if (t.adsPlayerHasStarted) { + t.media.play(); + } + }, + + adsPrerollMeta: function() { + + var t = this, + newDuration = 0; + + console.log('loadedmetadata', t.media.duration, t.adsCurrentMediaDuration); + + // if duration has been set, show that + if (t.options.duration > 0) { + newDuration = t.options.duration; + } else if (!isNaN(t.adsCurrentMediaDuration)) { + newDuration = t.adsCurrentMediaDuration; + } + + setTimeout(function() { + t.controls.find('.mejs-duration').html( + mejs.Utility.secondsToTimeCode(newDuration, t.options.alwaysShowHours || newDuration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) + ); + }, 250); + }, + + adsPrerollStarted: function() { + console.log('adsPrerollStarted'); + + var t = this; + t.media.removeEventListener('playing', t.adsPrerollStartedProxy); + + // turn off controls until the preroll is done + t.disableControls(); + t.hideControls(); + + // enable clicking through + if (t.options.adsPrerollAdUrl != '') { + t.adsLayer.show(); + t.adsLayer.find('a').attr('href', t.options.adsPrerollAdUrl); + } + + // possibly allow the skip button to work + if (t.options.adsPrerollAdEnableSkip) { + t.adsSkipBlock.show(); + + if (t.options.adsPrerollAdSkipSeconds > 0) { + t.adsSkipMessage.html('Skip in ' + t.options.adsPrerollAdSkipSeconds.toString() + ' seconds.').show(); + t.adsSkipButton.hide(); + } else { + t.adsSkipMessage.hide(); + t.adsSkipButton.show(); + } + } else { + t.adsSkipBlock.hide(); + } + + // send click events + t.container.trigger('mejsprerollstarted'); + }, + + adsPrerollUpdate: function() { + //console.log('adsPrerollUpdate'); + + var t = this; + + if (t.options.adsPrerollAdEnableSkip && t.options.adsPrerollAdSkipSeconds > 0) { + // update message + if (t.media.currentTime > t.options.adsPrerollAdSkipSeconds) { + t.adsSkipButton.show(); + t.adsSkipMessage.hide(); + } else { + t.adsSkipMessage.html('Skip in ' + Math.round( t.options.adsPrerollAdSkipSeconds - t.media.currentTime ).toString() + ' seconds.') + } + + } + + t.container.trigger('mejsprerolltimeupdate'); + }, + + adsPrerollEnded: function() { + console.log('adsPrerollEnded'); + + var t = this; + + t.container.trigger('mejsprerollended'); + + t.adRestoreMainMedia(); + }, + + adRestoreMainMedia: function() { + + console.log('adRestoreMainMedia', this.adsCurrentMediaUrl); + + var t = this; + + t.media.setSrc(t.adsCurrentMediaUrl); + setTimeout(function() { + t.media.load(); + t.media.play(); + }, 10); + + t.enableControls(); + t.showControls(); + + t.adsLayer.hide(); + + t.media.removeEventListener('ended', t.adsPrerollEndedProxy); + t.media.removeEventListener('loadedmetadata', t.adsPrerollMetaProxy); + t.media.removeEventListener('timeupdate', t.adsPrerollUpdateProxy); + + t.container.trigger('mejsprerollmainstarted'); + }, + + adsAdClick: function(e) { + console.log('adsAdClicked'); + + var t = this; + + if (t.media.paused) { + t.media.play(); + } else { + t.media.pause(); + } + + t.container.trigger('mejsprerolladsclicked'); + }, + + adsSkipClick: function() { + console.log('adsSkipClick'); + var t = this; + + t.container.trigger('mejsprerollskipclicked'); + t.container.trigger('mejsprerollended'); + + t.adRestoreMainMedia(); + }, + + // fires off fake XHR requests + adsLoadUrl: function(url) { + console.log('adsLoadUrl', url); + + var img = new Image(), + rnd = Math.round(Math.random()*100000); + + img.src = url + ((url.indexOf('?') > 0) ? '&' : '?') + 'random' + rnd + '=' + rnd; + img.loaded = function() { + img = null; + } + } + + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-backlight.js b/js/mediaelement/src/js/mep-feature-backlight.js new file mode 100644 index 0000000000000000000000000000000000000000..759c71afa5d9da32dd5946afa1328c51bac4b40b --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-backlight.js @@ -0,0 +1,435 @@ +(function($) { + + $.extend(mejs.MepDefaults, { + backlightBackground: [0,0,0], + backlightHorizontalLights: 5, + backlightVerticalLights: 5, + backlightSize: 50, + backlightTimeout: 200 + }); + + $.extend(MediaElementPlayer.prototype, { + buildbacklight : function(player, controls, layers, media) { + if (!player.isVideo) + return; + + //http://www.splashnology.com/blog/html5/382.html + + var + mediaContainer = player.container.find('.mejs-mediaelement').parent(), + border = $('<div class="mejs-border"></div>') + .prependTo(mediaContainer) + .css('position','absolute') + .css('top','-10px') + .css('left','-10px') + .css('border','solid 10px #010101') + .width(player.width).height(player.height), + glowBase = $('<div class="mejs-backlight-glow"></div>') + .prependTo(mediaContainer) + .css('position','absolute') + .css('display','none') + .css('top',0) + .css('left',0) + .width(player.width).height(player.height), + base = $('<div class="mejs-backlight"></div>') + .prependTo(mediaContainer) + .css('position','absolute') + .css('top',0) + .css('left',0) + .width(player.width).height(player.height), + + i, + copyCanvas = document.createElement('canvas'), + copyContext = copyCanvas.getContext('2d'), + pixels, + keepUpdating = true, + isActive = true, + timer = null, + glowCanvas = document.createElement('canvas'), + glowContext = glowCanvas.getContext('2d'), + size = player.options.backlightSize, + backgroundColor = player.options.backlightBackground, + gradient, + width = player.width, + height = player.height; + + // set sizes + copyCanvas.width = width; + copyCanvas.height = height; + glowCanvas.width = width + size + size; + glowCanvas.height = height + size + size; + + // draw glow overlay + // top + gradient = addGlow(backgroundColor,glowContext.createLinearGradient(size, size, size, 0)); + glowContext.fillStyle = gradient; + glowContext.fillRect(size, size, width, -size); + + // tr + gradient = addGlow(backgroundColor,glowContext.createRadialGradient(width+size, size, 0, width+size, size, size)); + glowContext.fillStyle = gradient; + glowContext.fillRect(width+size, size, size, -size); + + // right + gradient = addGlow(backgroundColor,glowContext.createLinearGradient(width+size, size, width+size+size, size)); + glowContext.fillStyle = gradient; + glowContext.fillRect(width+size, size, size, height); + + // br + gradient = addGlow(backgroundColor,glowContext.createRadialGradient(width+size, height+size, 0, width+size, height+size, size)); + glowContext.fillStyle = gradient; + glowContext.fillRect(width+size, height+size, size, size); + + // bottom + var gradient = addGlow(backgroundColor,glowContext.createLinearGradient(size, size+height, size, size+height+size)); + glowContext.fillStyle = gradient; + glowContext.fillRect(size, size+height, width, size); + + // bl + gradient = addGlow(backgroundColor,glowContext.createRadialGradient(size, height+size, 0, size, height+size, size)); + glowContext.fillStyle = gradient; + glowContext.fillRect(0, height+size, size, size); + + // left + gradient = addGlow(backgroundColor,glowContext.createLinearGradient(size, size, 0, size)); + glowContext.fillStyle = gradient; + glowContext.fillRect(size, size, -size, height); + + // tl + gradient = addGlow(backgroundColor,glowContext.createRadialGradient(size, size, 0, size, size, size)); + glowContext.fillStyle = gradient; + glowContext.fillRect(0, 0, size, size); + + $(glowCanvas) + .css('position','absolute') + .css('top',-size) + .css('left',-size) + .appendTo(glowBase); + + + // add toggle control + $('<div class="mejs-backlight-button mejs-backlight-active"><span></span></div>') + .appendTo(controls) + .click(function() { + if (isActive) { + delete timer; + timer = null; + base.hide(); + glowBase.hide(); + $(this) + .removeClass('mejs-backlight-active') + .addClass('mejs-backlight-inactive') + } else { + updateLights(); + base.show(); + glowBase.show(); + $(this) + .removeClass('mejs-backlight-inactive') + .addClass('mejs-backlight-active') + } + isActive = !isActive; + }); + + + // http://www.splashnology.com/blog/html5/382.html + function updateLights() { + + // get a copy of video + copyContext.drawImage(media, 0, 0, media.width, media.height); + + // create the gradient lights + addLights(base, copyCanvas, copyContext, + player.options.backlightVerticalLights, + player.options.backlightHorizontalLights, + player.options.backlightSize, + 30); + + if (keepUpdating && isActive) { + timer = setTimeout(updateLights, player.options.backlightTimeout); + } + } + + + + + //setTimeout(updateLights, timeOut); + + media.addEventListener('play',function() { + if (isActive) { + keepUpdating = true; + updateLights(); + glowBase.css('display',''); + } + }, false); + media.addEventListener('pause',function() { + keepUpdating = false; + }, false); + media.addEventListener('ended',function() { + keepUpdating = false; + }, false); + + } + }); + + function addLights(base, canvas, context, vBlocks, hBlocks, size, depth) { + base.empty(); + + var + lightsCanvas = document.createElement('canvas'), + lightsContext = lightsCanvas.getContext('2d'), + width = canvas.width, + height = canvas.height, + g, + topLights = getMidColors(canvas, context, hBlocks, depth, 'top'), + bottomLights = getMidColors(canvas, context, hBlocks, depth, 'bottom'), + leftLights = getMidColors(canvas, context, vBlocks, depth, 'left'), + rightLights = getMidColors(canvas, context, vBlocks, depth, 'right'), + corners = [], + stopSize = 0; + + lightsCanvas.width = width + size + size; + lightsCanvas.height = height + size + size; + lightsContext.globalCompositeOperation = 'xor'; //'darker'; //'lighter'; + + // draw four gradients + // create corners + corners.push(averageColor(topLights[topLights.length-1], rightLights[0]) ); + corners.push(averageColor(bottomLights[bottomLights.length-1], rightLights[rightLights.length-1]) ); + corners.push(averageColor(bottomLights[0], leftLights[leftLights.length-1]) ); + corners.push(averageColor(topLights[0], leftLights[0]) ); + + // top + stopSize = 1 / topLights.length; + gradient = context.createLinearGradient(size, size, width+size, size); + gradient.addColorStop(0, 'rgb(' + adjustColor(corners[3]).join(',') + ')'); + for (var i = 0, il = topLights.length; i < il; i++) { + gradient.addColorStop(i * stopSize + stopSize/2, 'rgb(' + adjustColor(topLights[i]).join(',') + ')'); + } + gradient.addColorStop(1.0, 'rgb(' + adjustColor(corners[0]).join(',') + ')'); + lightsContext.fillStyle = gradient; + lightsContext.fillRect(size, 0, width, size); + + // right + gradient = context.createLinearGradient(size+width, size, size+width, size+height); + gradient.addColorStop(0, 'rgb(' + adjustColor(corners[0]).join(',') + ')'); + for (var i = 0, il = rightLights.length; i < il; i++) { + gradient.addColorStop(i * stopSize + stopSize/2, 'rgb(' + adjustColor(rightLights[i]).join(',') + ')'); + } + gradient.addColorStop(1.0, 'rgb(' + adjustColor(corners[1]).join(',') + ')'); + lightsContext.fillStyle = gradient; + lightsContext.fillRect(size+width, size, size+width+size, height); + + + // bottom + gradient = context.createLinearGradient(size, size+height, size+width, size+height); + gradient.addColorStop(0, 'rgb(' + adjustColor(corners[2]).join(',') + ')'); + for (var i = 0, il = bottomLights.length; i < il; i++) { + gradient.addColorStop(i * stopSize + stopSize/2, 'rgb(' + adjustColor(bottomLights[i]).join(',') + ')'); + } + gradient.addColorStop(1.0, 'rgb(' + adjustColor(corners[1]).join(',') + ')'); + lightsContext.fillStyle = gradient; + lightsContext.fillRect(size, size+height, width, size); + + // left + gradient = context.createLinearGradient(size, size, size, size+height); + gradient.addColorStop(0, 'rgb(' + adjustColor(corners[3]).join(',') + ')'); + for (var i = 0, il = leftLights.length; i < il; i++) { + gradient.addColorStop(i * stopSize + stopSize/2, 'rgb(' + adjustColor(leftLights[i]).join(',') + ')'); + } + gradient.addColorStop(1.0, 'rgb(' + adjustColor(corners[2]).join(',') + ')'); + lightsContext.fillStyle = gradient; + lightsContext.fillRect(0, size, size, height); + + // corners + + // top right + lightsContext.fillStyle = 'rgb(' + adjustColor(corners[0]).join(',') + ')'; + lightsContext.fillRect(width+size, 0, size+width+size, size); + + // bottom right + lightsContext.fillStyle = 'rgb(' + adjustColor(corners[1]).join(',') + ')'; + lightsContext.fillRect(width+size, size+height, size+width+size, size+height+size); + + // bottom left + lightsContext.fillStyle = 'rgb(' + adjustColor(corners[2]).join(',') + ')'; + lightsContext.fillRect(0, size+height, size, size+height+size); + + // top left + lightsContext.fillStyle = 'rgb(' + adjustColor(corners[3]).join(',') + ')'; + lightsContext.fillRect(0, 0, size, size); + + + + + + $(lightsCanvas) + .css('position','absolute') + .css('top',-size) + .css('left',-size) + .appendTo(base); + } + + function addGlow(color, g) { + g.addColorStop(0.0, 'rgba(' + color.join(',') + ',0)'); + g.addColorStop(1.0, 'rgba(' + color.join(',') + ',1)'); + return g; + } + + + + + function getMidColors(canvas, context, blocks, blockDepth, side) { + var width = canvas.width, + height = canvas.height, + blockHeight = (side == 'top' || side == 'bottom') ? blockDepth : Math.ceil(height / blocks), // height of the analyzed block + blockWidth = (side == 'top' || side == 'bottom') ? Math.ceil(width / blocks) : blockDepth, + result = [], + imgdata, + i; + + if (side == 'top' || side == 'bottom') { + for (i = 0; i < blocks; i++) { + try { + imgdata = context.getImageData(i*blockWidth, (side == 'top') ? 0 : height - blockHeight , blockWidth, blockHeight); + result.push( + calcMidColor(imgdata.data) + ); + } catch (e) { + console.log(e); + } + } + } else { + + for (i = 0; i < blocks; i++) { + try { + imgdata = context.getImageData( (side == 'right') ? width - blockWidth : 0, i*blockHeight, blockWidth, blockHeight); + result.push( + calcMidColor(imgdata.data) + ); + } catch (e) { + console.log(e); + } + + } + } + + + return result; + } + + function averageColor(c1,c2) { + var result = + [(c1[0] + c2[0]) / 2, + (c1[1] + c2[1]) / 2, + (c1[2] + c2[2]) / 2]; + + return result; + } + + // average color for a block + function calcMidColorVertical(data, from, to) { + var result = [0, 0, 0]; + var totalPixels = (to - from) / 4; + + for (var i = from; i <= to; i += 4) { + result[0] += data[i]; + result[1] += data[i + 1]; + result[2] += data[i + 2]; + } + + result[0] = Math.round(result[0] / totalPixels); + result[1] = Math.round(result[1] / totalPixels); + result[2] = Math.round(result[2] / totalPixels); + + return result; + } + + // average color for a block + function calcMidColor(data) { + var result = [0, 0, 0]; + var totalPixels = data.length; + + for (var i = 0; i < totalPixels; i += 4) { + result[0] += data[i]; + result[1] += data[i + 1]; + result[2] += data[i + 2]; + } + + result[0] = Math.round(result[0] / totalPixels); + result[1] = Math.round(result[1] / totalPixels); + result[2] = Math.round(result[2] / totalPixels); + + return result; + } + + function adjustColor(color) { + //if (color[0] <= 2 && color[2] <= 2 && color[3] <= 2) + // return color; + + color = rgb2hsv(color); + color[1] = Math.min(100, color[1] * 1.2); //1.4); // saturation + color[2] = 80; //Math.min(100, color[2] * 2.7); //2.7); // brightness + return hsv2rgb(color); + } + + function rgb2hsv(color) { + var r = color[0] / 255, + g = color[1] / 255, + b = color[2] / 255, + + x, val, d1, d2, hue, sat, val; + + x = Math.min(Math.min(r, g), b); + val = Math.max(Math.max(r, g), b); + //if (x == val) + // throw Error('h is undefined'); + + d1 = (r == x) ? g-b : ((g == x) ? b-r : r-g); + d2 = (r == x) ? 3 : ((g == x) ? 5 : 1); + + hue = Math.floor((d2 - d1 / (val - x)) * 60) % 360; + sat = Math.floor(((val - x) / val) * 100); + val = Math.floor(val * 100); + return [hue, sat, val]; + } + + /** + * Convers HSV color to RGB model + * @param {Number[]} RGB color + * @return {Number[]} HSV color + */ + function hsv2rgb(color) { + var h = color[0], + s = color[1], + v = color[2]; + + var r, g, a, b, c, s = s / 100, v = v / 100, h = h / 360; + + if (s > 0) { + if (h >= 1) h=0; + + h = 6 * h; + var f = h - Math.floor(h); + a = Math.round(255 * v * (1 - s)); + b = Math.round(255 * v * (1 - (s * f))); + c = Math.round(255 * v * (1 - (s * (1 - f)))); + v = Math.round(255 * v); + + switch (Math.floor(h)) { + case 0: r = v; g = c; b = a; break; + case 1: r = b; g = v; b = a; break; + case 2: r = a; g = v; b = c; break; + case 3: r = a; g = b; b = v; break; + case 4: r = c; g = a; b = v; break; + case 5: r = v; g = a; b = b; break; + } + + return [r || 0, g || 0, b || 0]; + + } else { + v = Math.round(v * 255); + return [v, v, v]; + } + } + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-contextmenu.js b/js/mediaelement/src/js/mep-feature-contextmenu.js new file mode 100644 index 0000000000000000000000000000000000000000..572dc9feeea2017c9e6c4833ad85e861e3118f1f --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-contextmenu.js @@ -0,0 +1,197 @@ +/* +* ContextMenu Plugin +* +* +*/ + +(function($) { + +$.extend(mejs.MepDefaults, + { 'contextMenuItems': [ + // demo of a fullscreen option + { + render: function(player) { + + // check for fullscreen plugin + if (typeof player.enterFullScreen == 'undefined') + return null; + + if (player.isFullScreen) { + return mejs.i18n.t('Turn off Fullscreen'); + } else { + return mejs.i18n.t('Go Fullscreen'); + } + }, + click: function(player) { + if (player.isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + } + } + , + // demo of a mute/unmute button + { + render: function(player) { + if (player.media.muted) { + return mejs.i18n.t('Unmute'); + } else { + return mejs.i18n.t('Mute'); + } + }, + click: function(player) { + if (player.media.muted) { + player.setMuted(false); + } else { + player.setMuted(true); + } + } + }, + // separator + { + isSeparator: true + } + , + // demo of simple download video + { + render: function(player) { + return mejs.i18n.t('Download Video'); + }, + click: function(player) { + window.location.href = player.media.currentSrc; + } + } + ]} +); + + + $.extend(MediaElementPlayer.prototype, { + buildcontextmenu: function(player, controls, layers, media) { + + // create context menu + player.contextMenu = $('<div class="mejs-contextmenu"></div>') + .appendTo($('body')) + .hide(); + + // create events for showing context menu + player.container.bind('contextmenu', function(e) { + if (player.isContextMenuEnabled) { + e.preventDefault(); + player.renderContextMenu(e.clientX-1, e.clientY-1); + return false; + } + }); + player.container.bind('click', function() { + player.contextMenu.hide(); + }); + player.contextMenu.bind('mouseleave', function() { + + //console.log('context hover out'); + player.startContextMenuTimer(); + + }); + }, + + cleancontextmenu: function(player) { + player.contextMenu.remove(); + }, + + isContextMenuEnabled: true, + enableContextMenu: function() { + this.isContextMenuEnabled = true; + }, + disableContextMenu: function() { + this.isContextMenuEnabled = false; + }, + + contextMenuTimeout: null, + startContextMenuTimer: function() { + //console.log('startContextMenuTimer'); + + var t = this; + + t.killContextMenuTimer(); + + t.contextMenuTimer = setTimeout(function() { + t.hideContextMenu(); + t.killContextMenuTimer(); + }, 750); + }, + killContextMenuTimer: function() { + var timer = this.contextMenuTimer; + + //console.log('killContextMenuTimer', timer); + + if (timer != null) { + clearTimeout(timer); + delete timer; + timer = null; + } + }, + + hideContextMenu: function() { + this.contextMenu.hide(); + }, + + renderContextMenu: function(x,y) { + + // alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly + var t = this, + html = '', + items = t.options.contextMenuItems; + + for (var i=0, il=items.length; i<il; i++) { + + if (items[i].isSeparator) { + html += '<div class="mejs-contextmenu-separator"></div>'; + } else { + + var rendered = items[i].render(t); + + // render can return null if the item doesn't need to be used at the moment + if (rendered != null) { + html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>'; + } + } + } + + // position and show the context menu + t.contextMenu + .empty() + .append($(html)) + .css({top:y, left:x}) + .show(); + + // bind events + t.contextMenu.find('.mejs-contextmenu-item').each(function() { + + // which one is this? + var $dom = $(this), + itemIndex = parseInt( $dom.data('itemindex'), 10 ), + item = t.options.contextMenuItems[itemIndex]; + + // bind extra functionality? + if (typeof item.show != 'undefined') + item.show( $dom , t); + + // bind click action + $dom.click(function() { + // perform click action + if (typeof item.click != 'undefined') + item.click(t); + + // close + t.contextMenu.hide(); + }); + }); + + // stop the controls from hiding + setTimeout(function() { + t.killControlsTimer('rev3'); + }, 100); + + } + }); + +})(mejs.$); \ No newline at end of file diff --git a/js/mediaelement/src/js/mep-feature-endedhtml.js b/js/mediaelement/src/js/mep-feature-endedhtml.js new file mode 100644 index 0000000000000000000000000000000000000000..1a4fabb6678fa9e3214930cf5bb1e279a2003383 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-endedhtml.js @@ -0,0 +1,12 @@ +(function($) { + + $.extend(MediaElementPlayer.prototype, { + buildendedhtml: function(player, controls, layers, media) { + if (!player.isVideo) + return; + + // add postroll + } + }); + +})(mejs.$); \ No newline at end of file diff --git a/js/mediaelement/src/js/mep-feature-fullscreen.js b/js/mediaelement/src/js/mep-feature-fullscreen.js new file mode 100644 index 0000000000000000000000000000000000000000..e952c16e7cecd360fea832b767982cd586cef93b --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-fullscreen.js @@ -0,0 +1,478 @@ +(function($) { + + $.extend(mejs.MepDefaults, { + usePluginFullScreen: true, + newWindowCallback: function() { return '';}, + fullscreenText: mejs.i18n.t('Fullscreen') + }); + + $.extend(MediaElementPlayer.prototype, { + + isFullScreen: false, + + isNativeFullScreen: false, + + isInIframe: false, + + buildfullscreen: function(player, controls, layers, media) { + + if (!player.isVideo) + return; + + player.isInIframe = (window.location != window.parent.location); + + // native events + if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + + // chrome doesn't alays fire this in an iframe + var func = function(e) { + if (player.isFullScreen) { + if (mejs.MediaFeatures.isFullScreen()) { + player.isNativeFullScreen = true; + // reset the controls once we are fully in full screen + player.setControlsSize(); + } else { + player.isNativeFullScreen = false; + // when a user presses ESC + // make sure to put the player back into place + player.exitFullScreen(); + } + } + }; + + player.globalBind(mejs.MediaFeatures.fullScreenEventName, func); + } + + var t = this, + normalHeight = 0, + normalWidth = 0, + container = player.container, + fullscreenBtn = + $('<div class="mejs-button mejs-fullscreen-button">' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '" aria-label="' + t.options.fullscreenText + '"></button>' + + '</div>') + .appendTo(controls); + + if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) { + + fullscreenBtn.click(function() { + var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen; + + if (isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + }); + + } else { + + var hideTimeout = null, + supportsPointerEvents = (function() { + // TAKEN FROM MODERNIZR + var element = document.createElement('x'), + documentElement = document.documentElement, + getComputedStyle = window.getComputedStyle, + supports; + if(!('pointerEvents' in element.style)){ + return false; + } + element.style.pointerEvents = 'auto'; + element.style.pointerEvents = 'x'; + documentElement.appendChild(element); + supports = getComputedStyle && + getComputedStyle(element, '').pointerEvents === 'auto'; + documentElement.removeChild(element); + return !!supports; + })(); + + //console.log('supportsPointerEvents', supportsPointerEvents); + + if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :( + + // allows clicking through the fullscreen button and controls down directly to Flash + + /* + When a user puts his mouse over the fullscreen button, the controls are disabled + So we put a div over the video and another one on iether side of the fullscreen button + that caputre mouse movement + and restore the controls once the mouse moves outside of the fullscreen button + */ + + var fullscreenIsDisabled = false, + restoreControls = function() { + if (fullscreenIsDisabled) { + // hide the hovers + for (var i in hoverDivs) { + hoverDivs[i].hide(); + } + + // restore the control bar + fullscreenBtn.css('pointer-events', ''); + t.controls.css('pointer-events', ''); + + // prevent clicks from pausing video + t.media.removeEventListener('click', t.clickToPlayPauseCallback); + + // store for later + fullscreenIsDisabled = false; + } + }, + hoverDivs = {}, + hoverDivNames = ['top', 'left', 'right', 'bottom'], + i, len, + positionHoverDivs = function() { + var fullScreenBtnOffsetLeft = fullscreenBtn.offset().left - t.container.offset().left, + fullScreenBtnOffsetTop = fullscreenBtn.offset().top - t.container.offset().top, + fullScreenBtnWidth = fullscreenBtn.outerWidth(true), + fullScreenBtnHeight = fullscreenBtn.outerHeight(true), + containerWidth = t.container.width(), + containerHeight = t.container.height(); + + for (i in hoverDivs) { + hoverDivs[i].css({position: 'absolute', top: 0, left: 0}); //, backgroundColor: '#f00'}); + } + + // over video, but not controls + hoverDivs['top'] + .width( containerWidth ) + .height( fullScreenBtnOffsetTop ); + + // over controls, but not the fullscreen button + hoverDivs['left'] + .width( fullScreenBtnOffsetLeft ) + .height( fullScreenBtnHeight ) + .css({top: fullScreenBtnOffsetTop}); + + // after the fullscreen button + hoverDivs['right'] + .width( containerWidth - fullScreenBtnOffsetLeft - fullScreenBtnWidth ) + .height( fullScreenBtnHeight ) + .css({top: fullScreenBtnOffsetTop, + left: fullScreenBtnOffsetLeft + fullScreenBtnWidth}); + + // under the fullscreen button + hoverDivs['bottom'] + .width( containerWidth ) + .height( containerHeight - fullScreenBtnHeight - fullScreenBtnOffsetTop ) + .css({top: fullScreenBtnOffsetTop + fullScreenBtnHeight}); + }; + + t.globalBind('resize', function() { + positionHoverDivs(); + }); + + for (i = 0, len = hoverDivNames.length; i < len; i++) { + hoverDivs[hoverDivNames[i]] = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls).hide(); + } + + // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash + fullscreenBtn.on('mouseover',function() { + + if (!t.isFullScreen) { + + var buttonPos = fullscreenBtn.offset(), + containerPos = player.container.offset(); + + // move the button in Flash into place + media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false); + + // allows click through + fullscreenBtn.css('pointer-events', 'none'); + t.controls.css('pointer-events', 'none'); + + // restore click-to-play + t.media.addEventListener('click', t.clickToPlayPauseCallback); + + // show the divs that will restore things + for (i in hoverDivs) { + hoverDivs[i].show(); + } + + positionHoverDivs(); + + fullscreenIsDisabled = true; + } + + }); + + // restore controls anytime the user enters or leaves fullscreen + media.addEventListener('fullscreenchange', function(e) { + t.isFullScreen = !t.isFullScreen; + // don't allow plugin click to pause video - messes with + // plugin's controls + if (t.isFullScreen) { + t.media.removeEventListener('click', t.clickToPlayPauseCallback); + } else { + t.media.addEventListener('click', t.clickToPlayPauseCallback); + } + restoreControls(); + }); + + + // the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events + // so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button + + t.globalBind('mousemove', function(e) { + + // if the mouse is anywhere but the fullsceen button, then restore it all + if (fullscreenIsDisabled) { + + var fullscreenBtnPos = fullscreenBtn.offset(); + + + if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) || + e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true) + ) { + + fullscreenBtn.css('pointer-events', ''); + t.controls.css('pointer-events', ''); + + fullscreenIsDisabled = false; + } + } + }); + + + + } else { + + // the hover state will show the fullscreen button in Flash to hover up and click + + fullscreenBtn + .on('mouseover', function() { + + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + delete hideTimeout; + } + + var buttonPos = fullscreenBtn.offset(), + containerPos = player.container.offset(); + + media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true); + + }) + .on('mouseout', function() { + + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + delete hideTimeout; + } + + hideTimeout = setTimeout(function() { + media.hideFullscreenButton(); + }, 1500); + + + }); + } + } + + player.fullscreenBtn = fullscreenBtn; + + t.globalBind('keydown',function (e) { + if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) { + player.exitFullScreen(); + } + }); + + }, + + cleanfullscreen: function(player) { + player.exitFullScreen(); + }, + + containerSizeTimeout: null, + + enterFullScreen: function() { + + var t = this; + + // firefox+flash can't adjust plugin sizes without resetting :( + if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) { + //t.media.setFullscreen(true); + //player.isFullScreen = true; + return; + } + + // set it to not show scroll bars so 100% will work + $(document.documentElement).addClass('mejs-fullscreen'); + + // store sizing + normalHeight = t.container.height(); + normalWidth = t.container.width(); + + // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now) + if (t.media.pluginType === 'native') { + if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + + mejs.MediaFeatures.requestFullScreen(t.container[0]); + //return; + + if (t.isInIframe) { + // sometimes exiting from fullscreen doesn't work + // notably in Chrome <iframe>. Fixed in version 17 + setTimeout(function checkFullscreen() { + + if (t.isNativeFullScreen) { + var zoomMultiplier = window["devicePixelRatio"] || 1; + // Use a percent error margin since devicePixelRatio is a float and not exact. + var percentErrorMargin = 0.002; // 0.2% + var windowWidth = zoomMultiplier * $(window).width(); + var screenWidth = screen.width; + var absDiff = Math.abs(screenWidth - windowWidth); + var marginError = screenWidth * percentErrorMargin; + + // check if the video is suddenly not really fullscreen + if (absDiff > marginError) { + // manually exit + t.exitFullScreen(); + } else { + // test again + setTimeout(checkFullscreen, 500); + } + } + + + }, 500); + } + + } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) { + t.media.webkitEnterFullscreen(); + return; + } + } + + // check for iframe launch + if (t.isInIframe) { + var url = t.options.newWindowCallback(this); + + + if (url !== '') { + + // launch immediately + if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.pause(); + window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); + return; + } else { + setTimeout(function() { + if (!t.isNativeFullScreen) { + t.pause(); + window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); + } + }, 250); + } + } + + } + + // full window code + + + + // make full size + t.container + .addClass('mejs-container-fullscreen') + .width('100%') + .height('100%'); + //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); + + // Only needed for safari 5.1 native full screen, can cause display issues elsewhere + // Actually, it seems to be needed for IE8, too + //if (mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.containerSizeTimeout = setTimeout(function() { + t.container.css({width: '100%', height: '100%'}); + t.setControlsSize(); + }, 500); + //} + + if (t.media.pluginType === 'native') { + t.$media + .width('100%') + .height('100%'); + } else { + t.container.find('.mejs-shim') + .width('100%') + .height('100%'); + + //if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { + t.media.setVideoSize($(window).width(),$(window).height()); + //} + } + + t.layers.children('div') + .width('100%') + .height('100%'); + + if (t.fullscreenBtn) { + t.fullscreenBtn + .removeClass('mejs-fullscreen') + .addClass('mejs-unfullscreen'); + } + + t.setControlsSize(); + t.isFullScreen = true; + + t.container.find('.mejs-captions-text').css('font-size', screen.width / t.width * 1.00 * 100 + '%'); + t.container.find('.mejs-captions-position').css('bottom', '45px'); + }, + + exitFullScreen: function() { + + var t = this; + + // Prevent container from attempting to stretch a second time + clearTimeout(t.containerSizeTimeout); + + // firefox can't adjust plugins + if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { + t.media.setFullscreen(false); + //player.isFullScreen = false; + return; + } + + // come outo of native fullscreen + if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) { + mejs.MediaFeatures.cancelFullScreen(); + } + + // restore scroll bars to document + $(document.documentElement).removeClass('mejs-fullscreen'); + + t.container + .removeClass('mejs-container-fullscreen') + .width(normalWidth) + .height(normalHeight); + //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); + + if (t.media.pluginType === 'native') { + t.$media + .width(normalWidth) + .height(normalHeight); + } else { + t.container.find('.mejs-shim') + .width(normalWidth) + .height(normalHeight); + + t.media.setVideoSize(normalWidth, normalHeight); + } + + t.layers.children('div') + .width(normalWidth) + .height(normalHeight); + + t.fullscreenBtn + .removeClass('mejs-unfullscreen') + .addClass('mejs-fullscreen'); + + t.setControlsSize(); + t.isFullScreen = false; + + t.container.find('.mejs-captions-text').css('font-size',''); + t.container.find('.mejs-captions-position').css('bottom', ''); + } + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-googleanalytics.js b/js/mediaelement/src/js/mep-feature-googleanalytics.js new file mode 100644 index 0000000000000000000000000000000000000000..fdef1fecf747800b80795e26ea96b0c2ccbb44e2 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-googleanalytics.js @@ -0,0 +1,68 @@ +/* +* Google Analytics Plugin +* Requires +* +*/ + +(function($) { + +$.extend(mejs.MepDefaults, { + googleAnalyticsTitle: '', + googleAnalyticsCategory: 'Videos', + googleAnalyticsEventPlay: 'Play', + googleAnalyticsEventPause: 'Pause', + googleAnalyticsEventEnded: 'Ended', + googleAnalyticsEventTime: 'Time' +}); + + +$.extend(MediaElementPlayer.prototype, { + buildgoogleanalytics: function(player, controls, layers, media) { + + media.addEventListener('play', function() { + if (typeof _gaq != 'undefined') { + _gaq.push(['_trackEvent', + player.options.googleAnalyticsCategory, + player.options.googleAnalyticsEventPlay, + (player.options.googleAnalyticsTitle === '') ? player.currentSrc : player.options.googleAnalyticsTitle + ]); + } + }, false); + + media.addEventListener('pause', function() { + if (typeof _gaq != 'undefined') { + _gaq.push(['_trackEvent', + player.options.googleAnalyticsCategory, + player.options.googleAnalyticsEventPause, + (player.options.googleAnalyticsTitle === '') ? player.currentSrc : player.options.googleAnalyticsTitle + ]); + } + }, false); + + media.addEventListener('ended', function() { + if (typeof _gaq != 'undefined') { + _gaq.push(['_trackEvent', + player.options.googleAnalyticsCategory, + player.options.googleAnalyticsEventEnded, + (player.options.googleAnalyticsTitle === '') ? player.currentSrc : player.options.googleAnalyticsTitle + ]); + } + }, false); + + /* + media.addEventListener('timeupdate', function() { + if (typeof _gaq != 'undefined') { + _gaq.push(['_trackEvent', + player.options.googleAnalyticsCategory, + player.options.googleAnalyticsEventEnded, + player.options.googleAnalyticsTime, + (player.options.googleAnalyticsTitle === '') ? player.currentSrc : player.options.googleAnalyticsTitle, + player.currentTime + ]); + } + }, true); + */ + } +}); + +})(mejs.$); \ No newline at end of file diff --git a/js/mediaelement/src/js/mep-feature-loop.js b/js/mediaelement/src/js/mep-feature-loop.js new file mode 100644 index 0000000000000000000000000000000000000000..b30da6c421b9ba7005d1787ac4d4ed87dcb9d9c6 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-loop.js @@ -0,0 +1,26 @@ +(function($) { + // loop toggle + $.extend(MediaElementPlayer.prototype, { + buildloop: function(player, controls, layers, media) { + var + t = this, + // create the loop button + loop = + $('<div class="mejs-button mejs-loop-button ' + ((player.options.loop) ? 'mejs-loop-on' : 'mejs-loop-off') + '">' + + '<button type="button" aria-controls="' + t.id + '" title="Toggle Loop" aria-label="Toggle Loop"></button>' + + '</div>') + // append it to the toolbar + .appendTo(controls) + // add a click toggle event + .click(function() { + player.options.loop = !player.options.loop; + if (player.options.loop) { + loop.removeClass('mejs-loop-off').addClass('mejs-loop-on'); + } else { + loop.removeClass('mejs-loop-on').addClass('mejs-loop-off'); + } + }); + } + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-playlist.js b/js/mediaelement/src/js/mep-feature-playlist.js new file mode 100644 index 0000000000000000000000000000000000000000..24f3dca8d7db595e6eb53fd0207889c1a2fa7019 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-playlist.js @@ -0,0 +1,12 @@ +(function($) { + + $.extend(MediaElementPlayer.prototype, { + buildplaylist : function(player, controls, layers, media) { + if (!player.isVideo) + return; + + // add speed controls + } + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-playpause.js b/js/mediaelement/src/js/mep-feature-playpause.js new file mode 100644 index 0000000000000000000000000000000000000000..140ac528b848c8c97f09e84066dc74c34fa695b9 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-playpause.js @@ -0,0 +1,46 @@ +(function($) { + + $.extend(mejs.MepDefaults, { + playpauseText: mejs.i18n.t('Play/Pause') + }); + + // PLAY/pause BUTTON + $.extend(MediaElementPlayer.prototype, { + buildplaypause: function(player, controls, layers, media) { + var + t = this, + play = + $('<div class="mejs-button mejs-playpause-button mejs-play" >' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.playpauseText + '" aria-label="' + t.options.playpauseText + '"></button>' + + '</div>') + .appendTo(controls) + .click(function(e) { + e.preventDefault(); + + if (media.paused) { + media.play(); + } else { + media.pause(); + } + + return false; + }); + + media.addEventListener('play',function() { + play.removeClass('mejs-play').addClass('mejs-pause'); + }, false); + media.addEventListener('playing',function() { + play.removeClass('mejs-play').addClass('mejs-pause'); + }, false); + + + media.addEventListener('pause',function() { + play.removeClass('mejs-pause').addClass('mejs-play'); + }, false); + media.addEventListener('paused',function() { + play.removeClass('mejs-pause').addClass('mejs-play'); + }, false); + } + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-postroll.js b/js/mediaelement/src/js/mep-feature-postroll.js new file mode 100644 index 0000000000000000000000000000000000000000..5de9a342aca32653e27eeae5567fdd9e37c1c950 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-postroll.js @@ -0,0 +1,35 @@ +/** + * Postroll plugin + */ +(function($) { + + $.extend(mejs.MepDefaults, { + postrollCloseText: mejs.i18n.t('Close') + }); + + // Postroll + $.extend(MediaElementPlayer.prototype, { + buildpostroll: function(player, controls, layers, media) { + var + t = this, + postrollLink = t.container.find('link[rel="postroll"]').attr('href'); + + if (typeof postrollLink !== 'undefined') { + player.postroll = + $('<div class="mejs-postroll-layer mejs-layer"><a class="mejs-postroll-close" onclick="$(this).parent().hide();return false;">' + t.options.postrollCloseText + '</a><div class="mejs-postroll-layer-content"></div></div>').prependTo(layers).hide(); + + t.media.addEventListener('ended', function (e) { + $.ajax({ + dataType: 'html', + url: postrollLink, + success: function (data, textStatus) { + layers.find('.mejs-postroll-layer-content').html(data); + } + }); + player.postroll.show(); + }, false); + } + } + }); + +})(mejs.$); \ No newline at end of file diff --git a/js/mediaelement/src/js/mep-feature-progress.js b/js/mediaelement/src/js/mep-feature-progress.js new file mode 100644 index 0000000000000000000000000000000000000000..238e0914fe9ae9c9a2b0c8f1d6f00ecc48f83a98 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-progress.js @@ -0,0 +1,178 @@ +(function($) { + // progress/loaded bar + $.extend(MediaElementPlayer.prototype, { + buildprogress: function(player, controls, layers, media) { + + $('<div class="mejs-time-rail">'+ + '<span class="mejs-time-total">'+ + '<span class="mejs-time-buffering"></span>'+ + '<span class="mejs-time-loaded"></span>'+ + '<span class="mejs-time-current"></span>'+ + '<span class="mejs-time-handle"></span>'+ + '<span class="mejs-time-float">' + + '<span class="mejs-time-float-current">00:00</span>' + + '<span class="mejs-time-float-corner"></span>' + + '</span>'+ + '</span>'+ + '</div>') + .appendTo(controls); + controls.find('.mejs-time-buffering').hide(); + + var + t = this, + total = controls.find('.mejs-time-total'), + loaded = controls.find('.mejs-time-loaded'), + current = controls.find('.mejs-time-current'), + handle = controls.find('.mejs-time-handle'), + timefloat = controls.find('.mejs-time-float'), + timefloatcurrent = controls.find('.mejs-time-float-current'), + handleMouseMove = function (e) { + // mouse or touch position relative to the object + if (e.originalEvent.changedTouches) { + var x = e.originalEvent.changedTouches[0].pageX; + }else{ + var x = e.pageX; + } + + var offset = total.offset(), + width = total.outerWidth(true), + percentage = 0, + newTime = 0, + pos = 0; + + + if (media.duration) { + if (x < offset.left) { + x = offset.left; + } else if (x > width + offset.left) { + x = width + offset.left; + } + + pos = x - offset.left; + percentage = (pos / width); + newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; + + // seek to where the mouse is + if (mouseIsDown && newTime !== media.currentTime) { + media.setCurrentTime(newTime); + } + + // position floating time box + if (!mejs.MediaFeatures.hasTouch) { + timefloat.css('left', pos); + timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); + timefloat.show(); + } + } + }, + mouseIsDown = false, + mouseIsOver = false; + + // handle clicks + //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); + total + .bind('mousedown touchstart', function (e) { + // only handle left clicks or touch + if (e.which === 1 || e.which === 0) { + mouseIsDown = true; + handleMouseMove(e); + t.globalBind('mousemove.dur touchmove.dur', function(e) { + handleMouseMove(e); + }); + t.globalBind('mouseup.dur touchend.dur', function (e) { + mouseIsDown = false; + timefloat.hide(); + t.globalUnbind('.dur'); + }); + return false; + } + }) + .bind('mouseenter', function(e) { + mouseIsOver = true; + t.globalBind('mousemove.dur', function(e) { + handleMouseMove(e); + }); + if (!mejs.MediaFeatures.hasTouch) { + timefloat.show(); + } + }) + .bind('mouseleave',function(e) { + mouseIsOver = false; + if (!mouseIsDown) { + t.globalUnbind('.dur'); + timefloat.hide(); + } + }); + + // loading + media.addEventListener('progress', function (e) { + player.setProgressRail(e); + player.setCurrentRail(e); + }, false); + + // current time + media.addEventListener('timeupdate', function(e) { + player.setProgressRail(e); + player.setCurrentRail(e); + }, false); + + + // store for later use + t.loaded = loaded; + t.total = total; + t.current = current; + t.handle = handle; + }, + setProgressRail: function(e) { + + var + t = this, + target = (e != undefined) ? e.target : t.media, + percent = null; + + // newest HTML5 spec has buffered array (FF4, Webkit) + if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { + // TODO: account for a real array with multiple values (only Firefox 4 has this so far) + percent = target.buffered.end(0) / target.duration; + } + // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() + // to be anything other than 0. If the byte count is available we use this instead. + // Browsers that support the else if do not seem to have the bufferedBytes value and + // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. + else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) { + percent = target.bufferedBytes / target.bytesTotal; + } + // Firefox 3 with an Ogg file seems to go this way + else if (e && e.lengthComputable && e.total != 0) { + percent = e.loaded/e.total; + } + + // finally update the progress bar + if (percent !== null) { + percent = Math.min(1, Math.max(0, percent)); + // update loaded bar + if (t.loaded && t.total) { + t.loaded.width(t.total.width() * percent); + } + } + }, + setCurrentRail: function() { + + var t = this; + + if (t.media.currentTime != undefined && t.media.duration) { + + // update bar and handle + if (t.total && t.handle) { + var + newWidth = Math.round(t.total.width() * t.media.currentTime / t.media.duration), + handlePos = newWidth - Math.round(t.handle.outerWidth(true) / 2); + + t.current.width(newWidth); + t.handle.css('left', handlePos); + } + } + + } + }); +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-sourcechooser.js b/js/mediaelement/src/js/mep-feature-sourcechooser.js new file mode 100644 index 0000000000000000000000000000000000000000..fb68b3666cc6a215d380a449bf6ac2697b13026f --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-sourcechooser.js @@ -0,0 +1,92 @@ +// Source Chooser Plugin +(function($) { + + $.extend(mejs.MepDefaults, { + sourcechooserText: 'Source Chooser' + }); + + $.extend(MediaElementPlayer.prototype, { + buildsourcechooser: function(player, controls, layers, media) { + + var t = this; + + player.sourcechooserButton = + $('<div class="mejs-button mejs-sourcechooser-button">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.sourcechooserText + '" aria-label="' + t.options.sourcechooserText + '"></button>'+ + '<div class="mejs-sourcechooser-selector">'+ + '<ul>'+ + '</ul>'+ + '</div>'+ + '</div>') + .appendTo(controls) + + // hover + .hover(function() { + $(this).find('.mejs-sourcechooser-selector').css('visibility','visible'); + }, function() { + $(this).find('.mejs-sourcechooser-selector').css('visibility','hidden'); + }) + + // handle clicks to the language radio buttons + .delegate('input[type=radio]', 'click', function() { + var src = this.value; + + if (media.currentSrc != src) { + var currentTime = media.currentTime; + var paused = media.paused; + media.pause(); + media.setSrc(src); + + media.addEventListener('loadedmetadata', function(e) { + media.currentTime = currentTime; + }, true); + + var canPlayAfterSourceSwitchHandler = function(e) { + if (!paused) { + media.play(); + } + media.removeEventListener("canplay", canPlayAfterSourceSwitchHandler); + }; + media.addEventListener('canplay', canPlayAfterSourceSwitchHandler, true); + media.load(); + } + }); + + // add to list + for (var i in this.node.children) { + var src = this.node.children[i]; + if (src.nodeName === 'SOURCE' && (media.canPlayType(src.type) == 'probably' || media.canPlayType(src.type) == 'maybe')) { + player.addSourceButton(src.src, src.title, src.type, media.src == src.src); + } + } + + }, + + addSourceButton: function(src, label, type, isCurrent) { + var t = this; + if (label === '' || label == undefined) { + label = src; + } + type = type.split('/')[1]; + + t.sourcechooserButton.find('ul').append( + $('<li>'+ + '<input type="radio" name="' + t.id + '_sourcechooser" id="' + t.id + '_sourcechooser_' + label + type + '" value="' + src + '" ' + (isCurrent ? 'checked="checked"' : '') + ' />'+ + '<label for="' + t.id + '_sourcechooser_' + label + type + '">' + label + ' (' + type + ')</label>'+ + '</li>') + ); + + t.adjustSourcechooserBox(); + + }, + + adjustSourcechooserBox: function() { + var t = this; + // adjust the size of the outer box + t.sourcechooserButton.find('.mejs-sourcechooser-selector').height( + t.sourcechooserButton.find('.mejs-sourcechooser-selector ul').outerHeight(true) + ); + } + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-speed.js b/js/mediaelement/src/js/mep-feature-speed.js new file mode 100644 index 0000000000000000000000000000000000000000..8a6e738c16167d27cbf3757a5330810c4d09cd43 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-speed.js @@ -0,0 +1,60 @@ +(function($) { + + // Speed + $.extend(mejs.MepDefaults, { + + speeds: ['1.50', '1.25', '1.00', '0.75'], + + defaultSpeed: '1.00' + + }); + + $.extend(MediaElementPlayer.prototype, { + + buildspeed: function(player, controls, layers, media) { + var t = this; + + if (t.media.pluginType == 'native') { + var s = '<div class="mejs-button mejs-speed-button"><button type="button">'+t.options.defaultSpeed+'x</button><div class="mejs-speed-selector"><ul>'; + var i, ss; + + if ($.inArray(t.options.defaultSpeed, t.options.speeds) === -1) { + t.options.speeds.push(t.options.defaultSpeed); + } + + t.options.speeds.sort(function(a, b) { + return parseFloat(b) - parseFloat(a); + }); + + for (i = 0; i < t.options.speeds.length; i++) { + s += '<li><input type="radio" name="speed" value="' + t.options.speeds[i] + '" id="' + t.options.speeds[i] + '" '; + if (t.options.speeds[i] == t.options.defaultSpeed) { + s += 'checked=true '; + s += '/><label for="' + t.options.speeds[i] + '" class="mejs-speed-selected">'+ t.options.speeds[i] + 'x</label></li>'; + } else { + s += '/><label for="' + t.options.speeds[i] + '">'+ t.options.speeds[i] + 'x</label></li>'; + } + } + s += '</ul></div></div>'; + + player.speedButton = $(s).appendTo(controls); + + player.playbackspeed = t.options.defaultSpeed; + + player.speedButton + .on('click', 'input[type=radio]', function() { + player.playbackspeed = $(this).attr('value'); + media.playbackRate = parseFloat(player.playbackspeed); + player.speedButton.find('button').text(player.playbackspeed + 'x'); + player.speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected'); + player.speedButton.find('input[type=radio]:checked').next().addClass('mejs-speed-selected'); + }); + + ss = player.speedButton.find('.mejs-speed-selector'); + ss.height(this.speedButton.find('.mejs-speed-selector ul').outerHeight(true) + player.speedButton.find('.mejs-speed-translations').outerHeight(true)); + ss.css('top', (-1 * ss.height()) + 'px'); + } + } + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-stop.js b/js/mediaelement/src/js/mep-feature-stop.js new file mode 100644 index 0000000000000000000000000000000000000000..d70481ec60055c48b8a2c5c8de9311d1397f7754 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-stop.js @@ -0,0 +1,33 @@ +(function($) { + + $.extend(mejs.MepDefaults, { + stopText: 'Stop' + }); + + // STOP BUTTON + $.extend(MediaElementPlayer.prototype, { + buildstop: function(player, controls, layers, media) { + var t = this, + stop = + $('<div class="mejs-button mejs-stop-button mejs-stop">' + + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '" aria-label="' + t.options.stopText + '"></button>' + + '</div>') + .appendTo(controls) + .click(function() { + if (!media.paused) { + media.pause(); + } + if (media.currentTime > 0) { + media.setCurrentTime(0); + media.pause(); + controls.find('.mejs-time-current').width('0px'); + controls.find('.mejs-time-handle').css('left', '0px'); + controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); + controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); + layers.find('.mejs-poster').show(); + } + }); + } + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-time.js b/js/mediaelement/src/js/mep-feature-time.js new file mode 100644 index 0000000000000000000000000000000000000000..1c75d3cf71ce074fa1007e209ae5180d07f53c14 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-time.js @@ -0,0 +1,84 @@ +(function($) { + + // options + $.extend(mejs.MepDefaults, { + duration: -1, + timeAndDurationSeparator: '<span> | </span>' + }); + + + // current and duration 00:00 / 00:00 + $.extend(MediaElementPlayer.prototype, { + buildcurrent: function(player, controls, layers, media) { + var t = this; + + $('<div class="mejs-time">'+ + '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') + + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')+ '</span>'+ + '</div>') + .appendTo(controls); + + t.currenttime = t.controls.find('.mejs-currenttime'); + + media.addEventListener('timeupdate',function() { + player.updateCurrent(); + }, false); + }, + + + buildduration: function(player, controls, layers, media) { + var t = this; + + if (controls.children().last().find('.mejs-currenttime').length > 0) { + $(t.options.timeAndDurationSeparator + + '<span class="mejs-duration">' + + (t.options.duration > 0 ? + mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : + ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) + ) + + '</span>') + .appendTo(controls.find('.mejs-time')); + } else { + + // add class to current time + controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); + + $('<div class="mejs-time mejs-duration-container">'+ + '<span class="mejs-duration">' + + (t.options.duration > 0 ? + mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : + ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) + ) + + '</span>' + + '</div>') + .appendTo(controls); + } + + t.durationD = t.controls.find('.mejs-duration'); + + media.addEventListener('timeupdate',function() { + player.updateDuration(); + }, false); + }, + + updateCurrent: function() { + var t = this; + + if (t.currenttime) { + t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); + } + }, + + updateDuration: function() { + var t = this; + + //Toggle the long video class if the video is longer than an hour. + t.container.toggleClass("mejs-long-video", t.media.duration > 3600); + + if (t.durationD && (t.options.duration > 0 || t.media.duration)) { + t.durationD.html(mejs.Utility.secondsToTimeCode(t.options.duration > 0 ? t.options.duration : t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); + } + } + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-tracks.js b/js/mediaelement/src/js/mep-feature-tracks.js new file mode 100644 index 0000000000000000000000000000000000000000..6a62c811fb4c1a826b346c01054845e185e3d3e2 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-tracks.js @@ -0,0 +1,677 @@ +(function($) { + + // add extra default options + $.extend(mejs.MepDefaults, { + // this will automatically turn on a <track> + startLanguage: '', + + tracksText: mejs.i18n.t('Captions/Subtitles'), + + // option to remove the [cc] button when no <track kind="subtitles"> are present + hideCaptionsButtonWhenEmpty: true, + + // If true and we only have one track, change captions to popup + toggleCaptionsButtonWhenOnlyOne: false, + + // #id or .class + slidesSelector: '' + }); + + $.extend(MediaElementPlayer.prototype, { + + hasChapters: false, + + buildtracks: function(player, controls, layers, media) { + if (player.tracks.length === 0) + return; + + var t = this, + i, + options = ''; + + if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide + for (i = t.domNode.textTracks.length - 1; i >= 0; i--) { + t.domNode.textTracks[i].mode = "hidden"; + } + } + player.chapters = + $('<div class="mejs-chapters mejs-layer"></div>') + .prependTo(layers).hide(); + player.captions = + $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover"><span class="mejs-captions-text"></span></div></div>') + .prependTo(layers).hide(); + player.captionsText = player.captions.find('.mejs-captions-text'); + player.captionsButton = + $('<div class="mejs-button mejs-captions-button">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '" aria-label="' + t.options.tracksText + '"></button>'+ + '<div class="mejs-captions-selector">'+ + '<ul>'+ + '<li>'+ + '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' + + '<label for="' + player.id + '_captions_none">' + mejs.i18n.t('None') +'</label>'+ + '</li>' + + '</ul>'+ + '</div>'+ + '</div>') + .appendTo(controls); + + + var subtitleCount = 0; + for (i=0; i<player.tracks.length; i++) { + if (player.tracks[i].kind == 'subtitles') { + subtitleCount++; + } + } + + // if only one language then just make the button a toggle + if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitleCount == 1){ + // click + player.captionsButton.on('click',function() { + if (player.selectedTrack === null) { + lang = player.tracks[0].srclang; + } else { + lang = 'none'; + } + player.setTrack(lang); + }); + } else { + // hover or keyboard focus + player.captionsButton.on( 'mouseenter focusin', function() { + $(this).find('.mejs-captions-selector').css('visibility','visible'); + }) + + // handle clicks to the language radio buttons + .on('click','input[type=radio]',function() { + lang = this.value; + player.setTrack(lang); + }); + + player.captionsButton.on( 'mouseleave focusout', function() { + $(this).find(".mejs-captions-selector").css("visibility","hidden"); + }); + + } + + if (!player.options.alwaysShowControls) { + // move with controls + player.container + .bind('controlsshown', function () { + // push captions above controls + player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); + + }) + .bind('controlshidden', function () { + if (!media.paused) { + // move back to normal place + player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); + } + }); + } else { + player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); + } + + player.trackToLoad = -1; + player.selectedTrack = null; + player.isLoadingTrack = false; + + // add to list + for (i=0; i<player.tracks.length; i++) { + if (player.tracks[i].kind == 'subtitles') { + player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label); + } + } + + // start loading tracks + player.loadNextTrack(); + + media.addEventListener('timeupdate',function(e) { + player.displayCaptions(); + }, false); + + if (player.options.slidesSelector !== '') { + player.slidesContainer = $(player.options.slidesSelector); + + media.addEventListener('timeupdate',function(e) { + player.displaySlides(); + }, false); + + } + + media.addEventListener('loadedmetadata', function(e) { + player.displayChapters(); + }, false); + + player.container.hover( + function () { + // chapters + if (player.hasChapters) { + player.chapters.css('visibility','visible'); + player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight()); + } + }, + function () { + if (player.hasChapters && !media.paused) { + player.chapters.fadeOut(200, function() { + $(this).css('visibility','hidden'); + $(this).css('display','block'); + }); + } + }); + + // check for autoplay + if (player.node.getAttribute('autoplay') !== null) { + player.chapters.css('visibility','hidden'); + } + }, + + setTrack: function(lang){ + + var t = this, + i; + + if (lang == 'none') { + t.selectedTrack = null; + t.captionsButton.removeClass('mejs-captions-enabled'); + } else { + for (i=0; i<t.tracks.length; i++) { + if (t.tracks[i].srclang == lang) { + if (t.selectedTrack === null) + t.captionsButton.addClass('mejs-captions-enabled'); + t.selectedTrack = t.tracks[i]; + t.captions.attr('lang', t.selectedTrack.srclang); + t.displayCaptions(); + break; + } + } + } + }, + + loadNextTrack: function() { + var t = this; + + t.trackToLoad++; + if (t.trackToLoad < t.tracks.length) { + t.isLoadingTrack = true; + t.loadTrack(t.trackToLoad); + } else { + // add done? + t.isLoadingTrack = false; + + t.checkForTracks(); + } + }, + + loadTrack: function(index){ + var + t = this, + track = t.tracks[index], + after = function() { + + track.isLoaded = true; + + // create button + //t.addTrackButton(track.srclang); + t.enableTrackButton(track.srclang, track.label); + + t.loadNextTrack(); + + }; + + + $.ajax({ + url: track.src, + dataType: "text", + success: function(d) { + + // parse the loaded file + if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) { + track.entries = mejs.TrackFormatParser.dfxp.parse(d); + } else { + track.entries = mejs.TrackFormatParser.webvtt.parse(d); + } + + after(); + + if (track.kind == 'chapters') { + t.media.addEventListener('play', function(e) { + if (t.media.duration > 0) { + t.displayChapters(track); + } + }, false); + } + + if (track.kind == 'slides') { + t.setupSlides(track); + } + }, + error: function() { + t.loadNextTrack(); + } + }); + }, + + enableTrackButton: function(lang, label) { + var t = this; + + if (label === '') { + label = mejs.language.codes[lang] || lang; + } + + t.captionsButton + .find('input[value=' + lang + ']') + .prop('disabled',false) + .siblings('label') + .html( label ); + + // auto select + if (t.options.startLanguage == lang) { + $('#' + t.id + '_captions_' + lang).prop('checked', true).trigger('click'); + } + + t.adjustLanguageBox(); + }, + + addTrackButton: function(lang, label) { + var t = this; + if (label === '') { + label = mejs.language.codes[lang] || lang; + } + + t.captionsButton.find('ul').append( + $('<li>'+ + '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' + + '<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+ + '</li>') + ); + + t.adjustLanguageBox(); + + // remove this from the dropdownlist (if it exists) + t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove(); + }, + + adjustLanguageBox:function() { + var t = this; + // adjust the size of the outer box + t.captionsButton.find('.mejs-captions-selector').height( + t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) + + t.captionsButton.find('.mejs-captions-translations').outerHeight(true) + ); + }, + + checkForTracks: function() { + var + t = this, + hasSubtitles = false; + + // check if any subtitles + if (t.options.hideCaptionsButtonWhenEmpty) { + for (i=0; i<t.tracks.length; i++) { + if (t.tracks[i].kind == 'subtitles') { + hasSubtitles = true; + break; + } + } + + if (!hasSubtitles) { + t.captionsButton.hide(); + t.setControlsSize(); + } + } + }, + + displayCaptions: function() { + + if (typeof this.tracks == 'undefined') + return; + + var + t = this, + i, + track = t.selectedTrack; + + if (track !== null && track.isLoaded) { + for (i=0; i<track.entries.times.length; i++) { + if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop) { + // Set the line before the timecode as a class so the cue can be targeted if needed + t.captionsText.html(track.entries.text[i]).attr('class', 'mejs-captions-text ' + (track.entries.times[i].identifier || '')); + t.captions.show().height(0); + return; // exit out if one is visible; + } + } + t.captions.hide(); + } else { + t.captions.hide(); + } + }, + + setupSlides: function(track) { + var t = this; + + t.slides = track; + t.slides.entries.imgs = [t.slides.entries.text.length]; + t.showSlide(0); + + }, + + showSlide: function(index) { + if (typeof this.tracks == 'undefined' || typeof this.slidesContainer == 'undefined') { + return; + } + + var t = this, + url = t.slides.entries.text[index], + img = t.slides.entries.imgs[index]; + + if (typeof img == 'undefined' || typeof img.fadeIn == 'undefined') { + + t.slides.entries.imgs[index] = img = $('<img src="' + url + '">') + .on('load', function() { + img.appendTo(t.slidesContainer) + .hide() + .fadeIn() + .siblings(':visible') + .fadeOut(); + + }); + + } else { + + if (!img.is(':visible') && !img.is(':animated')) { + + //console.log('showing existing slide'); + + img.fadeIn() + .siblings(':visible') + .fadeOut(); + } + } + + }, + + displaySlides: function() { + + if (typeof this.slides == 'undefined') + return; + + var + t = this, + slides = t.slides, + i; + + for (i=0; i<slides.entries.times.length; i++) { + if (t.media.currentTime >= slides.entries.times[i].start && t.media.currentTime <= slides.entries.times[i].stop){ + + t.showSlide(i); + + return; // exit out if one is visible; + } + } + }, + + displayChapters: function() { + var + t = this, + i; + + for (i=0; i<t.tracks.length; i++) { + if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) { + t.drawChapters(t.tracks[i]); + t.hasChapters = true; + break; + } + } + }, + + drawChapters: function(chapters) { + var + t = this, + i, + dur, + //width, + //left, + percent = 0, + usedPercent = 0; + + t.chapters.empty(); + + for (i=0; i<chapters.entries.times.length; i++) { + dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; + percent = Math.floor(dur / t.media.duration * 100); + if (percent + usedPercent > 100 || // too large + i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in + { + percent = 100 - usedPercent; + } + //width = Math.floor(t.width * dur / t.media.duration); + //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); + //if (left + width > t.width) { + // width = t.width - left; + //} + + t.chapters.append( $( + '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + + '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + + '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + + '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + + '</div>' + + '</div>')); + usedPercent += percent; + } + + t.chapters.find('div.mejs-chapter').click(function() { + t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); + if (t.media.paused) { + t.media.play(); + } + }); + + t.chapters.show(); + } + }); + + + + mejs.language = { + codes: { + af:'Afrikaans', + sq:'Albanian', + ar:'Arabic', + be:'Belarusian', + bg:'Bulgarian', + ca:'Catalan', + zh:'Chinese', + 'zh-cn':'Chinese Simplified', + 'zh-tw':'Chinese Traditional', + hr:'Croatian', + cs:'Czech', + da:'Danish', + nl:'Dutch', + en:'English', + et:'Estonian', + fl:'Filipino', + fi:'Finnish', + fr:'French', + gl:'Galician', + de:'German', + el:'Greek', + ht:'Haitian Creole', + iw:'Hebrew', + hi:'Hindi', + hu:'Hungarian', + is:'Icelandic', + id:'Indonesian', + ga:'Irish', + it:'Italian', + ja:'Japanese', + ko:'Korean', + lv:'Latvian', + lt:'Lithuanian', + mk:'Macedonian', + ms:'Malay', + mt:'Maltese', + no:'Norwegian', + fa:'Persian', + pl:'Polish', + pt:'Portuguese', + // 'pt-pt':'Portuguese (Portugal)', + ro:'Romanian', + ru:'Russian', + sr:'Serbian', + sk:'Slovak', + sl:'Slovenian', + es:'Spanish', + sw:'Swahili', + sv:'Swedish', + tl:'Tagalog', + th:'Thai', + tr:'Turkish', + uk:'Ukrainian', + vi:'Vietnamese', + cy:'Welsh', + yi:'Yiddish' + } + }; + + /* + Parses WebVTT format which should be formatted as + ================================ + WEBVTT + + 1 + 00:00:01,1 --> 00:00:05,000 + A line of text + + 2 + 00:01:15,1 --> 00:02:05,000 + A second line of text + + =============================== + + Adapted from: http://www.delphiki.com/html5/playr + */ + mejs.TrackFormatParser = { + webvtt: { + pattern_timecode: /^((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ((?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, + + parse: function(trackText) { + var + i = 0, + lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/), + entries = {text:[], times:[]}, + timecode, + text, + identifier; + for(; i<lines.length; i++) { + timecode = this.pattern_timecode.exec(lines[i]); + + if (timecode && i<lines.length) { + if ((i - 1) >= 0 && lines[i - 1] !== '') { + identifier = lines[i - 1]; + } + i++; + // grab all the (possibly multi-line) text that follows + text = lines[i]; + i++; + while(lines[i] !== '' && i<lines.length){ + text = text + '\n' + lines[i]; + i++; + } + text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); + // Text is in a different array so I can use .join + entries.text.push(text); + entries.times.push( + { + identifier: identifier, + start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) === 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]), + stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]), + settings: timecode[5] + }); + } + identifier = ''; + } + return entries; + } + }, + // Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420 + dfxp: { + parse: function(trackText) { + trackText = $(trackText).filter("tt"); + var + i = 0, + container = trackText.children("div").eq(0), + lines = container.find("p"), + styleNode = trackText.find("#" + container.attr("style")), + styles, + begin, + end, + text, + entries = {text:[], times:[]}; + + + if (styleNode.length) { + var attributes = styleNode.removeAttr("id").get(0).attributes; + if (attributes.length) { + styles = {}; + for (i = 0; i < attributes.length; i++) { + styles[attributes[i].name.split(":")[1]] = attributes[i].value; + } + } + } + + for(i = 0; i<lines.length; i++) { + var style; + var _temp_times = { + start: null, + stop: null, + style: null + }; + if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin")); + if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end")); + if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end")); + if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin")); + if (styles) { + style = ""; + for (var _style in styles) { + style += _style + ":" + styles[_style] + ";"; + } + } + if (style) _temp_times.style = style; + if (_temp_times.start === 0) _temp_times.start = 0.200; + entries.times.push(_temp_times); + text = $.trim(lines.eq(i).html()).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); + entries.text.push(text); + if (entries.times.start === 0) entries.times.start = 2; + } + return entries; + } + }, + split2: function (text, regex) { + // normal version for compliant browsers + // see below for IE fix + return text.split(regex); + } + }; + + // test for browsers with bad String.split method. + if ('x\n\ny'.split(/\n/gi).length != 3) { + // add super slow IE8 and below version + mejs.TrackFormatParser.split2 = function(text, regex) { + var + parts = [], + chunk = '', + i; + + for (i=0; i<text.length; i++) { + chunk += text.substring(i,i+1); + if (regex.test(chunk)) { + parts.push(chunk.replace(regex, '')); + chunk = ''; + } + } + parts.push(chunk); + return parts; + }; + } + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-feature-universalgoogleanalytics.js b/js/mediaelement/src/js/mep-feature-universalgoogleanalytics.js new file mode 100644 index 0000000000000000000000000000000000000000..431b393aa2a963f715646072aa7dcdc56a9228ac --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-universalgoogleanalytics.js @@ -0,0 +1,67 @@ +/* +* analytics.js Google Analytics Plugin +* Requires JQuery +*/ + +(function($) { + +$.extend(mejs.MepDefaults, { + googleAnalyticsTitle: '', + googleAnalyticsCategory: 'Videos', + googleAnalyticsEventPlay: 'Play', + googleAnalyticsEventPause: 'Pause', + googleAnalyticsEventEnded: 'Ended', + googleAnalyticsEventTime: 'Time' +}); + + +$.extend(MediaElementPlayer.prototype, { + builduniversalgoogleanalytics: function(player, controls, layers, media) { + + media.addEventListener('play', function() { + if (typeof ga != 'undefined') { + ga('send', 'event', + player.options.googleAnalyticsCategory, + player.options.googleAnalyticsEventPlay, + (player.options.googleAnalyticsTitle === '') ? player.currentSrc : player.options.googleAnalyticsTitle + ); + } + }, false); + + media.addEventListener('pause', function() { + if (typeof ga != 'undefined') { + ga('send', 'event', + player.options.googleAnalyticsCategory, + player.options.googleAnalyticsEventPause, + (player.options.googleAnalyticsTitle === '') ? player.currentSrc : player.options.googleAnalyticsTitle + ); + } + }, false); + + media.addEventListener('ended', function() { + if (typeof ga != 'undefined') { + ga('send', 'event', + player.options.googleAnalyticsCategory, + player.options.googleAnalyticsEventEnded, + (player.options.googleAnalyticsTitle === '') ? player.currentSrc : player.options.googleAnalyticsTitle + ); + } + }, false); + + /* + media.addEventListener('timeupdate', function() { + if (typeof ga != 'undefined') { + ga('send', 'event', + player.options.googleAnalyticsCategory, + player.options.googleAnalyticsEventEnded, + player.options.googleAnalyticsTime, + (player.options.googleAnalyticsTitle === '') ? player.currentSrc : player.options.googleAnalyticsTitle, + player.currentTime + ); + } + }, true); + */ + } +}); + +})(mejs.$); \ No newline at end of file diff --git a/js/mediaelement/src/js/mep-feature-visualcontrols.js b/js/mediaelement/src/js/mep-feature-visualcontrols.js new file mode 100644 index 0000000000000000000000000000000000000000..893aabe0e125cc5b289ed8c9a0944e72ea233d65 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-visualcontrols.js @@ -0,0 +1,10 @@ +(function($) { + + MediaElementPlayer.prototype.buildvisualcontrols = function(player, controls, layers, media) { + if (!player.isVideo) + return; + + // add visual controls (HSV) + } + +})(mejs.$); \ No newline at end of file diff --git a/js/mediaelement/src/js/mep-feature-volume.js b/js/mediaelement/src/js/mep-feature-volume.js new file mode 100644 index 0000000000000000000000000000000000000000..978dec5c7e74f0b52c51a8ac2a9b64df3ea60ae0 --- /dev/null +++ b/js/mediaelement/src/js/mep-feature-volume.js @@ -0,0 +1,225 @@ +(function($) { + + $.extend(mejs.MepDefaults, { + muteText: mejs.i18n.t('Mute Toggle'), + hideVolumeOnTouchDevices: true, + + audioVolume: 'horizontal', + videoVolume: 'vertical' + }); + + $.extend(MediaElementPlayer.prototype, { + buildvolume: function(player, controls, layers, media) { + + // Android and iOS don't support volume controls + if ((mejs.MediaFeatures.isAndroid || mejs.MediaFeatures.isiOS) && this.options.hideVolumeOnTouchDevices) + return; + + var t = this, + mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume, + mute = (mode == 'horizontal') ? + + // horizontal version + $('<div class="mejs-button mejs-volume-button mejs-mute">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+ + '</div>' + + '<div class="mejs-horizontal-volume-slider">'+ // outer background + '<div class="mejs-horizontal-volume-total"></div>'+ // line background + '<div class="mejs-horizontal-volume-current"></div>'+ // current volume + '<div class="mejs-horizontal-volume-handle"></div>'+ // handle + '</div>' + ) + .appendTo(controls) : + + // vertical version + $('<div class="mejs-button mejs-volume-button mejs-mute">'+ + '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+ + '<div class="mejs-volume-slider">'+ // outer background + '<div class="mejs-volume-total"></div>'+ // line background + '<div class="mejs-volume-current"></div>'+ // current volume + '<div class="mejs-volume-handle"></div>'+ // handle + '</div>'+ + '</div>') + .appendTo(controls), + volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'), + volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'), + volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'), + volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'), + + positionVolumeHandle = function(volume, secondTry) { + + if (!volumeSlider.is(':visible') && typeof secondTry == 'undefined') { + volumeSlider.show(); + positionVolumeHandle(volume, true); + volumeSlider.hide() + return; + } + + // correct to 0-1 + volume = Math.max(0,volume); + volume = Math.min(volume,1); + + // ajust mute button style + if (volume == 0) { + mute.removeClass('mejs-mute').addClass('mejs-unmute'); + } else { + mute.removeClass('mejs-unmute').addClass('mejs-mute'); + } + + // position slider + if (mode == 'vertical') { + var + + // height of the full size volume slider background + totalHeight = volumeTotal.height(), + + // top/left of full size volume slider background + totalPosition = volumeTotal.position(), + + // the new top position based on the current volume + // 70% volume on 100px height == top:30px + newTop = totalHeight - (totalHeight * volume); + + // handle + volumeHandle.css('top', Math.round(totalPosition.top + newTop - (volumeHandle.height() / 2))); + + // show the current visibility + volumeCurrent.height(totalHeight - newTop ); + volumeCurrent.css('top', totalPosition.top + newTop); + } else { + var + + // height of the full size volume slider background + totalWidth = volumeTotal.width(), + + // top/left of full size volume slider background + totalPosition = volumeTotal.position(), + + // the new left position based on the current volume + newLeft = totalWidth * volume; + + // handle + volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2))); + + // rezize the current part of the volume bar + volumeCurrent.width( Math.round(newLeft) ); + } + }, + handleVolumeMove = function(e) { + + var volume = null, + totalOffset = volumeTotal.offset(); + + // calculate the new volume based on the moust position + if (mode == 'vertical') { + + var + railHeight = volumeTotal.height(), + totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), + newY = e.pageY - totalOffset.top; + + volume = (railHeight - newY) / railHeight; + + // the controls just hide themselves (usually when mouse moves too far up) + if (totalOffset.top == 0 || totalOffset.left == 0) + return; + + } else { + var + railWidth = volumeTotal.width(), + newX = e.pageX - totalOffset.left; + + volume = newX / railWidth; + } + + // ensure the volume isn't outside 0-1 + volume = Math.max(0,volume); + volume = Math.min(volume,1); + + // position the slider and handle + positionVolumeHandle(volume); + + // set the media object (this will trigger the volumechanged event) + if (volume == 0) { + media.setMuted(true); + } else { + media.setMuted(false); + } + media.setVolume(volume); + }, + mouseIsDown = false, + mouseIsOver = false; + + // SLIDER + + mute + .hover(function() { + volumeSlider.show(); + mouseIsOver = true; + }, function() { + mouseIsOver = false; + + if (!mouseIsDown && mode == 'vertical') { + volumeSlider.hide(); + } + }); + + volumeSlider + .bind('mouseover', function() { + mouseIsOver = true; + }) + .bind('mousedown', function (e) { + handleVolumeMove(e); + t.globalBind('mousemove.vol', function(e) { + handleVolumeMove(e); + }); + t.globalBind('mouseup.vol', function () { + mouseIsDown = false; + t.globalUnbind('.vol'); + + if (!mouseIsOver && mode == 'vertical') { + volumeSlider.hide(); + } + }); + mouseIsDown = true; + + return false; + }); + + + // MUTE button + mute.find('button').click(function() { + media.setMuted( !media.muted ); + }); + + // listen for volume change events from other sources + media.addEventListener('volumechange', function(e) { + if (!mouseIsDown) { + if (media.muted) { + positionVolumeHandle(0); + mute.removeClass('mejs-mute').addClass('mejs-unmute'); + } else { + positionVolumeHandle(media.volume); + mute.removeClass('mejs-unmute').addClass('mejs-mute'); + } + } + }, false); + + if (t.container.is(':visible')) { + // set initial volume + positionVolumeHandle(player.options.startVolume); + + // mutes the media and sets the volume icon muted if the initial volume is set to 0 + if (player.options.startVolume === 0) { + media.setMuted(true); + } + + // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements + if (media.pluginType === 'native') { + media.setVolume(player.options.startVolume); + } + } + } + }); + +})(mejs.$); diff --git a/js/mediaelement/src/js/mep-header.js b/js/mediaelement/src/js/mep-header.js new file mode 100644 index 0000000000000000000000000000000000000000..c743ffe09c330750189fd7670971d82a166bf8af --- /dev/null +++ b/js/mediaelement/src/js/mep-header.js @@ -0,0 +1,12 @@ +/*! + * + * MediaElementPlayer + * http://mediaelementjs.com/ + * + * Creates a controller bar for HTML5 <video> add <audio> tags + * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) + * + * Copyright 2010-2013, John Dyer (http://j.hn/) + * License: MIT + * + */ \ No newline at end of file diff --git a/js/mediaelement/src/js/mep-library.js b/js/mediaelement/src/js/mep-library.js new file mode 100644 index 0000000000000000000000000000000000000000..9bdb99be2f35d6f8a9859656b9d41cb3451257b3 --- /dev/null +++ b/js/mediaelement/src/js/mep-library.js @@ -0,0 +1,5 @@ +if (typeof jQuery != 'undefined') { + mejs.$ = jQuery; +} else if (typeof ender != 'undefined') { + mejs.$ = ender; +} \ No newline at end of file diff --git a/js/mediaelement/src/js/mep-player.js b/js/mediaelement/src/js/mep-player.js new file mode 100644 index 0000000000000000000000000000000000000000..cff95dcf115614d0e80ab16f29892cf0fb09a0c2 --- /dev/null +++ b/js/mediaelement/src/js/mep-player.js @@ -0,0 +1,1290 @@ +(function ($) { + + // default player values + mejs.MepDefaults = { + // url to poster (to fix iOS 3.x) + poster: '', + // When the video is ended, we can show the poster. + showPosterWhenEnded: false, + // default if the <video width> is not specified + defaultVideoWidth: 480, + // default if the <video height> is not specified + defaultVideoHeight: 270, + // if set, overrides <video width> + videoWidth: -1, + // if set, overrides <video height> + videoHeight: -1, + // default if the user doesn't specify + defaultAudioWidth: 400, + // default if the user doesn't specify + defaultAudioHeight: 30, + + // default amount to move back when back key is pressed + defaultSeekBackwardInterval: function(media) { + return (media.duration * 0.05); + }, + // default amount to move forward when forward key is pressed + defaultSeekForwardInterval: function(media) { + return (media.duration * 0.05); + }, + + // set dimensions via JS instead of CSS + setDimensions: true, + + // width of audio player + audioWidth: -1, + // height of audio player + audioHeight: -1, + // initial volume when the player starts (overrided by user cookie) + startVolume: 0.8, + // useful for <audio> player loops + loop: false, + // rewind to beginning when media ends + autoRewind: true, + // resize to media dimensions + enableAutosize: true, + // forces the hour marker (##:00:00) + alwaysShowHours: false, + + // show framecount in timecode (##:00:00:00) + showTimecodeFrameCount: false, + // used when showTimecodeFrameCount is set to true + framesPerSecond: 25, + + // automatically calculate the width of the progress bar based on the sizes of other elements + autosizeProgress : true, + // Hide controls when playing and mouse is not over the video + alwaysShowControls: false, + // Display the video control + hideVideoControlsOnLoad: false, + // Enable click video element to toggle play/pause + clickToPlayPause: true, + // force iPad's native controls + iPadUseNativeControls: false, + // force iPhone's native controls + iPhoneUseNativeControls: false, + // force Android's native controls + AndroidUseNativeControls: false, + // features to show + features: ['playpause','current','progress','duration','tracks','volume','fullscreen'], + // only for dynamic + isVideo: true, + + // turns keyboard support on and off for this instance + enableKeyboard: true, + + // whenthis player starts, it will pause other players + pauseOtherPlayers: true, + + // array of keyboard actions such as play pause + keyActions: [ + { + keys: [ + 32, // SPACE + 179 // GOOGLE play/pause button + ], + action: function(player, media) { + if (media.paused || media.ended) { + player.play(); + } else { + player.pause(); + } + } + }, + { + keys: [38], // UP + action: function(player, media) { + player.container.find('.mejs-volume-slider').css('display','block'); + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + var newVolume = Math.min(media.volume + 0.1, 1); + media.setVolume(newVolume); + } + }, + { + keys: [40], // DOWN + action: function(player, media) { + player.container.find('.mejs-volume-slider').css('display','block'); + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + var newVolume = Math.max(media.volume - 0.1, 0); + media.setVolume(newVolume); + } + }, + { + keys: [ + 37, // LEFT + 227 // Google TV rewind + ], + action: function(player, media) { + if (!isNaN(media.duration) && media.duration > 0) { + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + // 5% + var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0); + media.setCurrentTime(newTime); + } + } + }, + { + keys: [ + 39, // RIGHT + 228 // Google TV forward + ], + action: function(player, media) { + if (!isNaN(media.duration) && media.duration > 0) { + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + + // 5% + var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration); + media.setCurrentTime(newTime); + } + } + }, + { + keys: [70], // F + action: function(player, media) { + if (typeof player.enterFullScreen != 'undefined') { + if (player.isFullScreen) { + player.exitFullScreen(); + } else { + player.enterFullScreen(); + } + } + } + }, + { + keys: [77], // M + action: function(player, media) { + player.container.find('.mejs-volume-slider').css('display','block'); + if (player.isVideo) { + player.showControls(); + player.startControlsTimer(); + } + if (player.media.muted) { + player.setMuted(false); + } else { + player.setMuted(true); + } + } + } + ] + }; + + mejs.mepIndex = 0; + + mejs.players = {}; + + // wraps a MediaElement object in player controls + mejs.MediaElementPlayer = function(node, o) { + // enforce object, even without "new" (via John Resig) + if ( !(this instanceof mejs.MediaElementPlayer) ) { + return new mejs.MediaElementPlayer(node, o); + } + + var t = this; + + // these will be reset after the MediaElement.success fires + t.$media = t.$node = $(node); + t.node = t.media = t.$media[0]; + + // check for existing player + if (typeof t.node.player != 'undefined') { + return t.node.player; + } else { + // attach player to DOM node for reference + t.node.player = t; + } + + + // try to get options from data-mejsoptions + if (typeof o == 'undefined') { + o = t.$node.data('mejsoptions'); + } + + // extend default options + t.options = $.extend({},mejs.MepDefaults,o); + + // unique ID + t.id = 'mep_' + mejs.mepIndex++; + + // add to player array (for focus events) + mejs.players[t.id] = t; + + // start up + t.init(); + + return t; + }; + + // actual player + mejs.MediaElementPlayer.prototype = { + + hasFocus: false, + + controlsAreVisible: true, + + init: function() { + + var + t = this, + mf = mejs.MediaFeatures, + // options for MediaElement (shim) + meOptions = $.extend(true, {}, t.options, { + success: function(media, domNode) { t.meReady(media, domNode); }, + error: function(e) { t.handleError(e);} + }), + tagName = t.media.tagName.toLowerCase(); + + t.isDynamic = (tagName !== 'audio' && tagName !== 'video'); + + if (t.isDynamic) { + // get video from src or href? + t.isVideo = t.options.isVideo; + } else { + t.isVideo = (tagName !== 'audio' && t.options.isVideo); + } + + // use native controls in iPad, iPhone, and Android + if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) { + + // add controls and stop + t.$media.attr('controls', 'controls'); + + // attempt to fix iOS 3 bug + //t.$media.removeAttr('poster'); + // no Issue found on iOS3 -ttroxell + + // override Apple's autoplay override for iPads + if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { + t.play(); + } + + } else if (mf.isAndroid && t.options.AndroidUseNativeControls) { + + // leave default player + + } else { + + // DESKTOP: use MediaElementPlayer controls + + // remove native controls + t.$media.removeAttr('controls'); + + // build container + t.container = + $('<div id="' + t.id + '" class="mejs-container ' + (mejs.MediaFeatures.svg ? 'svg' : 'no-svg') + '">'+ + '<div class="mejs-inner">'+ + '<div class="mejs-mediaelement"></div>'+ + '<div class="mejs-layers"></div>'+ + '<div class="mejs-controls"></div>'+ + '<div class="mejs-clear"></div>'+ + '</div>' + + '</div>') + .addClass(t.$media[0].className) + .insertBefore(t.$media); + + // add classes for user and content + t.container.addClass( + (mf.isAndroid ? 'mejs-android ' : '') + + (mf.isiOS ? 'mejs-ios ' : '') + + (mf.isiPad ? 'mejs-ipad ' : '') + + (mf.isiPhone ? 'mejs-iphone ' : '') + + (t.isVideo ? 'mejs-video ' : 'mejs-audio ') + ); + + + // move the <video/video> tag into the right spot + if (mf.isiOS) { + + // sadly, you can't move nodes in iOS, so we have to destroy and recreate it! + var $newMedia = t.$media.clone(); + + t.container.find('.mejs-mediaelement').append($newMedia); + + t.$media.remove(); + t.$node = t.$media = $newMedia; + t.node = t.media = $newMedia[0] + + } else { + + // normal way of moving it into place (doesn't work on iOS) + t.container.find('.mejs-mediaelement').append(t.$media); + } + + // find parts + t.controls = t.container.find('.mejs-controls'); + t.layers = t.container.find('.mejs-layers'); + + // determine the size + + /* size priority: + (1) videoWidth (forced), + (2) style="width;height;" + (3) width attribute, + (4) defaultVideoWidth (for unspecified cases) + */ + + var tagType = (t.isVideo ? 'video' : 'audio'), + capsTagName = tagType.substring(0,1).toUpperCase() + tagType.substring(1); + + + + if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) { + t.width = t.options[tagType + 'Width']; + } else if (t.media.style.width !== '' && t.media.style.width !== null) { + t.width = t.media.style.width; + } else if (t.media.getAttribute('width') !== null) { + t.width = t.$media.attr('width'); + } else { + t.width = t.options['default' + capsTagName + 'Width']; + } + + if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) { + t.height = t.options[tagType + 'Height']; + } else if (t.media.style.height !== '' && t.media.style.height !== null) { + t.height = t.media.style.height; + } else if (t.$media[0].getAttribute('height') !== null) { + t.height = t.$media.attr('height'); + } else { + t.height = t.options['default' + capsTagName + 'Height']; + } + + // set the size, while we wait for the plugins to load below + t.setPlayerSize(t.width, t.height); + + // create MediaElementShim + meOptions.pluginWidth = t.width; + meOptions.pluginHeight = t.height; + } + + // create MediaElement shim + mejs.MediaElement(t.$media[0], meOptions); + + if (typeof(t.container) != 'undefined' && t.controlsAreVisible){ + // controls are shown when loaded + t.container.trigger('controlsshown'); + } + }, + + showControls: function(doAnimation) { + var t = this; + + doAnimation = typeof doAnimation == 'undefined' || doAnimation; + + if (t.controlsAreVisible) + return; + + if (doAnimation) { + t.controls + .css('visibility','visible') + .stop(true, true).fadeIn(200, function() { + t.controlsAreVisible = true; + t.container.trigger('controlsshown'); + }); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control') + .css('visibility','visible') + .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); + + } else { + t.controls + .css('visibility','visible') + .css('display','block'); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control') + .css('visibility','visible') + .css('display','block'); + + t.controlsAreVisible = true; + t.container.trigger('controlsshown'); + } + + t.setControlsSize(); + + }, + + hideControls: function(doAnimation) { + var t = this; + + doAnimation = typeof doAnimation == 'undefined' || doAnimation; + + if (!t.controlsAreVisible || t.options.alwaysShowControls) + return; + + if (doAnimation) { + // fade out main controls + t.controls.stop(true, true).fadeOut(200, function() { + $(this) + .css('visibility','hidden') + .css('display','block'); + + t.controlsAreVisible = false; + t.container.trigger('controlshidden'); + }); + + // any additional controls people might add and want to hide + t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() { + $(this) + .css('visibility','hidden') + .css('display','block'); + }); + } else { + + // hide main controls + t.controls + .css('visibility','hidden') + .css('display','block'); + + // hide others + t.container.find('.mejs-control') + .css('visibility','hidden') + .css('display','block'); + + t.controlsAreVisible = false; + t.container.trigger('controlshidden'); + } + }, + + controlsTimer: null, + + startControlsTimer: function(timeout) { + + var t = this; + + timeout = typeof timeout != 'undefined' ? timeout : 1500; + + t.killControlsTimer('start'); + + t.controlsTimer = setTimeout(function() { + //console.log('timer fired'); + t.hideControls(); + t.killControlsTimer('hide'); + }, timeout); + }, + + killControlsTimer: function(src) { + + var t = this; + + if (t.controlsTimer !== null) { + clearTimeout(t.controlsTimer); + delete t.controlsTimer; + t.controlsTimer = null; + } + }, + + controlsEnabled: true, + + disableControls: function() { + var t= this; + + t.killControlsTimer(); + t.hideControls(false); + this.controlsEnabled = false; + }, + + enableControls: function() { + var t= this; + + t.showControls(false); + + t.controlsEnabled = true; + }, + + + // Sets up all controls and events + meReady: function(media, domNode) { + + + var t = this, + mf = mejs.MediaFeatures, + autoplayAttr = domNode.getAttribute('autoplay'), + autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'), + featureIndex, + feature; + + // make sure it can't create itself again if a plugin reloads + if (t.created) { + return; + } else { + t.created = true; + } + + t.media = media; + t.domNode = domNode; + + if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) { + + // two built in features + t.buildposter(t, t.controls, t.layers, t.media); + t.buildkeyboard(t, t.controls, t.layers, t.media); + t.buildoverlays(t, t.controls, t.layers, t.media); + + // grab for use by features + t.findTracks(); + + // add user-defined features/controls + for (featureIndex in t.options.features) { + feature = t.options.features[featureIndex]; + if (t['build' + feature]) { + try { + t['build' + feature](t, t.controls, t.layers, t.media); + } catch (e) { + // TODO: report control error + //throw e; + console.log('error building ' + feature); + console.log(e); + } + } + } + + t.container.trigger('controlsready'); + + // reset all layers and controls + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + + + // controls fade + if (t.isVideo) { + + if (mejs.MediaFeatures.hasTouch) { + + // for touch devices (iOS, Android) + // show/hide without animation on touch + + t.$media.bind('touchstart', function() { + + + // toggle controls + if (t.controlsAreVisible) { + t.hideControls(false); + } else { + if (t.controlsEnabled) { + t.showControls(false); + } + } + }); + + } else { + + // create callback here since it needs access to current + // MediaElement object + t.clickToPlayPauseCallback = function() { + //console.log('media clicked', t.media, t.media.paused); + + if (t.options.clickToPlayPause) { + if (t.media.paused) { + t.play(); + } else { + t.pause(); + } + } + }; + + // click to play/pause + t.media.addEventListener('click', t.clickToPlayPauseCallback, false); + + // show/hide controls + t.container + .bind('mouseenter mouseover', function () { + if (t.controlsEnabled) { + if (!t.options.alwaysShowControls ) { + t.killControlsTimer('enter'); + t.showControls(); + t.startControlsTimer(2500); + } + } + }) + .bind('mousemove', function() { + if (t.controlsEnabled) { + if (!t.controlsAreVisible) { + t.showControls(); + } + if (!t.options.alwaysShowControls) { + t.startControlsTimer(2500); + } + } + }) + .bind('mouseleave', function () { + if (t.controlsEnabled) { + if (!t.media.paused && !t.options.alwaysShowControls) { + t.startControlsTimer(1000); + } + } + }); + } + + if(t.options.hideVideoControlsOnLoad) { + t.hideControls(false); + } + + // check for autoplay + if (autoplay && !t.options.alwaysShowControls) { + t.hideControls(); + } + + // resizer + if (t.options.enableAutosize) { + t.media.addEventListener('loadedmetadata', function(e) { + // if the <video height> was not set and the options.videoHeight was not set + // then resize to the real dimensions + if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) { + t.setPlayerSize(e.target.videoWidth, e.target.videoHeight); + t.setControlsSize(); + t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight); + } + }, false); + } + } + + // EVENTS + + // FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them) + media.addEventListener('play', function() { + var playerIndex; + + // go through all other players + for (playerIndex in mejs.players) { + var p = mejs.players[playerIndex]; + if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) { + p.pause(); + } + p.hasFocus = false; + } + + t.hasFocus = true; + },false); + + + // ended for all + t.media.addEventListener('ended', function (e) { + if(t.options.autoRewind) { + try{ + t.media.setCurrentTime(0); + } catch (exp) { + + } + } + t.media.pause(); + + if (t.setProgressRail) { + t.setProgressRail(); + } + if (t.setCurrentRail) { + t.setCurrentRail(); + } + + if (t.options.loop) { + t.play(); + } else if (!t.options.alwaysShowControls && t.controlsEnabled) { + t.showControls(); + } + }, false); + + // resize on the first play + t.media.addEventListener('loadedmetadata', function(e) { + if (t.updateDuration) { + t.updateDuration(); + } + if (t.updateCurrent) { + t.updateCurrent(); + } + + if (!t.isFullScreen) { + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + } + }, false); + + + // webkit has trouble doing this without a delay + setTimeout(function () { + t.setPlayerSize(t.width, t.height); + t.setControlsSize(); + }, 50); + + // adjust controls whenever window sizes (used to be in fullscreen only) + t.globalBind('resize', function() { + + // don't resize for fullscreen mode + if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) { + t.setPlayerSize(t.width, t.height); + } + + // always adjust controls + t.setControlsSize(); + }); + + // TEMP: needs to be moved somewhere else + if (t.media.pluginType == 'youtube' && t.options.autoplay) { + //LOK-Soft: added t.options.autoplay to if -- I can only guess this is for hiding play button when autoplaying youtube, general hiding play button layer causes missing button on player load + t.container.find('.mejs-overlay-play').hide(); + } + } + + // force autoplay for HTML5 + if (autoplay && media.pluginType == 'native') { + t.play(); + } + + + if (t.options.success) { + + if (typeof t.options.success == 'string') { + window[t.options.success](t.media, t.domNode, t); + } else { + t.options.success(t.media, t.domNode, t); + } + } + }, + + handleError: function(e) { + var t = this; + + t.controls.hide(); + + // Tell user that the file cannot be played + if (t.options.error) { + t.options.error(e); + } + }, + + setPlayerSize: function(width,height) { + var t = this; + + if( !t.options.setDimensions ) { + return false; + } + + if (typeof width != 'undefined') { + t.width = width; + } + + if (typeof height != 'undefined') { + t.height = height; + } + + // detect 100% mode - use currentStyle for IE since css() doesn't return percentages + if (t.height.toString().indexOf('%') > 0 || t.$node.css('max-width') === '100%' || (t.$node[0].currentStyle && t.$node[0].currentStyle.maxWidth === '100%')) { + + // do we have the native dimensions yet? + var nativeWidth = (function() { + if (t.isVideo) { + if (t.media.videoWidth && t.media.videoWidth > 0) { + return t.media.videoWidth; + } else if (t.media.getAttribute('width') !== null) { + return t.media.getAttribute('width'); + } else { + return t.options.defaultVideoWidth; + } + } else { + return t.options.defaultAudioWidth; + } + })(); + + var nativeHeight = (function() { + if (t.isVideo) { + if (t.media.videoHeight && t.media.videoHeight > 0) { + return t.media.videoHeight; + } else if (t.media.getAttribute('height') !== null) { + return t.media.getAttribute('height'); + } else { + return t.options.defaultVideoHeight; + } + } else { + return t.options.defaultAudioHeight; + } + })(); + + var + parentWidth = t.container.parent().closest(':visible').width(), + parentHeight = t.container.parent().closest(':visible').height(), + newHeight = t.isVideo || !t.options.autosizeProgress ? parseInt(parentWidth * nativeHeight/nativeWidth, 10) : nativeHeight; + + // When we use percent, the newHeight can't be calculated so we get the container height + if(isNaN(newHeight) || ( parentHeight != 0 && newHeight > parentHeight )) { + newHeight = parentHeight; + } + + if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) { + parentWidth = $(window).width(); + newHeight = $(window).height(); + } + + if ( newHeight && parentWidth ) { + // set outer container size + t.container + .width(parentWidth) + .height(newHeight); + + // set native <video> or <audio> and shims + t.$media.add(t.container.find('.mejs-shim')) + .width('100%') + .height('100%'); + + // if shim is ready, send the size to the embeded plugin + if (t.isVideo) { + if (t.media.setVideoSize) { + t.media.setVideoSize(parentWidth, newHeight); + } + } + + // set the layers + t.layers.children('.mejs-layer') + .width('100%') + .height('100%'); + } + + + } else { + + t.container + .width(t.width) + .height(t.height); + + t.layers.children('.mejs-layer') + .width(t.width) + .height(t.height); + + } + + // special case for big play button so it doesn't go over the controls area + var playLayer = t.layers.find('.mejs-overlay-play'), + playButton = playLayer.find('.mejs-overlay-button'); + + playLayer.height(t.container.height() - t.controls.height()); + playButton.css('margin-top', '-' + (playButton.height()/2 - t.controls.height()/2).toString() + 'px' ); + + }, + + setControlsSize: function() { + var t = this, + usedWidth = 0, + railWidth = 0, + rail = t.controls.find('.mejs-time-rail'), + total = t.controls.find('.mejs-time-total'), + current = t.controls.find('.mejs-time-current'), + loaded = t.controls.find('.mejs-time-loaded'), + others = rail.siblings(), + lastControl = others.last(), + lastControlPosition = null; + + // skip calculation if hidden + if (!t.container.is(':visible') || !rail.length || !rail.is(':visible')) { + return; + } + + + // allow the size to come from custom CSS + if (t.options && !t.options.autosizeProgress) { + // Also, frontends devs can be more flexible + // due the opportunity of absolute positioning. + railWidth = parseInt(rail.css('width')); + } + + // attempt to autosize + if (railWidth === 0 || !railWidth) { + + // find the size of all the other controls besides the rail + others.each(function() { + var $this = $(this); + if ($this.css('position') != 'absolute' && $this.is(':visible')) { + usedWidth += $(this).outerWidth(true); + } + }); + + // fit the rail into the remaining space + railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width()); + } + + // resize the rail, + // but then check if the last control (say, the fullscreen button) got pushed down + // this often happens when zoomed + do { + // outer area + rail.width(railWidth); + // dark space + total.width(railWidth - (total.outerWidth(true) - total.width())); + + if (lastControl.css('position') != 'absolute') { + lastControlPosition = lastControl.position(); + railWidth--; + } + } while (lastControlPosition != null && lastControlPosition.top > 0 && railWidth > 0); + + if (t.setProgressRail) + t.setProgressRail(); + if (t.setCurrentRail) + t.setCurrentRail(); + }, + + + buildposter: function(player, controls, layers, media) { + var t = this, + poster = + $('<div class="mejs-poster mejs-layer">' + + '</div>') + .appendTo(layers), + posterUrl = player.$media.attr('poster'); + + // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) + if (player.options.poster !== '') { + posterUrl = player.options.poster; + } + + // second, try the real poster + if (posterUrl !== '' && posterUrl != null) { + t.setPoster(posterUrl); + } else { + poster.hide(); + } + + media.addEventListener('play',function() { + poster.hide(); + }, false); + + if(player.options.showPosterWhenEnded && player.options.autoRewind){ + media.addEventListener('ended',function() { + poster.show(); + }, false); + } + }, + + setPoster: function(url) { + var t = this, + posterDiv = t.container.find('.mejs-poster'), + posterImg = posterDiv.find('img'); + + if (posterImg.length == 0) { + posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv); + } + + posterImg.attr('src', url); + posterDiv.css({'background-image' : 'url(' + url + ')'}); + }, + + buildoverlays: function(player, controls, layers, media) { + var t = this; + if (!player.isVideo) + return; + + var + loading = + $('<div class="mejs-overlay mejs-layer">'+ + '<div class="mejs-overlay-loading"><span></span></div>'+ + '</div>') + .hide() // start out hidden + .appendTo(layers), + error = + $('<div class="mejs-overlay mejs-layer">'+ + '<div class="mejs-overlay-error"></div>'+ + '</div>') + .hide() // start out hidden + .appendTo(layers), + // this needs to come last so it's on top + bigPlay = + $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ + '<div class="mejs-overlay-button"></div>'+ + '</div>') + .appendTo(layers) + .bind('click', function() { // Removed 'touchstart' due issues on Samsung Android devices where a tap on bigPlay started and immediately stopped the video + if (t.options.clickToPlayPause) { + if (media.paused) { + media.play(); + } + } + }); + + /* + if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) { + bigPlay.remove(); + loading.remove(); + } + */ + + + // show/hide big play button + media.addEventListener('play',function() { + bigPlay.hide(); + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.hide(); + }, false); + + media.addEventListener('playing', function() { + bigPlay.hide(); + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.hide(); + }, false); + + media.addEventListener('seeking', function() { + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + + media.addEventListener('seeked', function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + }, false); + + media.addEventListener('pause',function() { + if (!mejs.MediaFeatures.isiPhone) { + bigPlay.show(); + } + }, false); + + media.addEventListener('waiting', function() { + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + + + // show/hide loading + media.addEventListener('loadeddata',function() { + // for some reason Chrome is firing this event + //if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') + // return; + + loading.show(); + controls.find('.mejs-time-buffering').show(); + }, false); + media.addEventListener('canplay',function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + }, false); + + // error handling + media.addEventListener('error',function() { + loading.hide(); + controls.find('.mejs-time-buffering').hide(); + error.show(); + error.find('mejs-overlay-error').html("Error loading this resource"); + }, false); + + media.addEventListener('keydown', function(e) { + t.onkeydown(player, media, e); + }, false); + }, + + buildkeyboard: function(player, controls, layers, media) { + + var t = this; + + // listen for key presses + t.globalBind('keydown', function(e) { + return t.onkeydown(player, media, e); + }); + + // check if someone clicked outside a player region, then kill its focus + t.globalBind('click', function(event) { + player.hasFocus = $(event.target).closest('.mejs-container').length != 0; + }); + + }, + onkeydown: function(player, media, e) { + if (player.hasFocus && player.options.enableKeyboard) { + // find a matching key + for (var i = 0, il = player.options.keyActions.length; i < il; i++) { + var keyAction = player.options.keyActions[i]; + + for (var j = 0, jl = keyAction.keys.length; j < jl; j++) { + if (e.keyCode == keyAction.keys[j]) { + if (typeof(e.preventDefault) == "function") e.preventDefault(); + keyAction.action(player, media, e.keyCode); + return false; + } + } + } + } + + return true; + }, + + findTracks: function() { + var t = this, + tracktags = t.$media.find('track'); + + // store for use by plugins + t.tracks = []; + tracktags.each(function(index, track) { + + track = $(track); + + t.tracks.push({ + srclang: (track.attr('srclang')) ? track.attr('srclang').toLowerCase() : '', + src: track.attr('src'), + kind: track.attr('kind'), + label: track.attr('label') || '', + entries: [], + isLoaded: false + }); + }); + }, + changeSkin: function(className) { + this.container[0].className = 'mejs-container ' + className; + this.setPlayerSize(this.width, this.height); + this.setControlsSize(); + }, + play: function() { + this.load(); + this.media.play(); + }, + pause: function() { + try { + this.media.pause(); + } catch (e) {} + }, + load: function() { + if (!this.isLoaded) { + this.media.load(); + } + + this.isLoaded = true; + }, + setMuted: function(muted) { + this.media.setMuted(muted); + }, + setCurrentTime: function(time) { + this.media.setCurrentTime(time); + }, + getCurrentTime: function() { + return this.media.currentTime; + }, + setVolume: function(volume) { + this.media.setVolume(volume); + }, + getVolume: function() { + return this.media.volume; + }, + setSrc: function(src) { + this.media.setSrc(src); + }, + remove: function() { + var t = this, featureIndex, feature; + + // invoke features cleanup + for (featureIndex in t.options.features) { + feature = t.options.features[featureIndex]; + if (t['clean' + feature]) { + try { + t['clean' + feature](t); + } catch (e) { + // TODO: report control error + //throw e; + //console.log('error building ' + feature); + //console.log(e); + } + } + } + + // grab video and put it back in place + if (!t.isDynamic) { + t.$media.prop('controls', true); + // detach events from the video + // TODO: detach event listeners better than this; + // also detach ONLY the events attached by this plugin! + t.$node.clone().insertBefore(t.container).show(); + t.$node.remove(); + } else { + t.$node.insertBefore(t.container); + } + + if (t.media.pluginType !== 'native') { + t.media.remove(); + } + + // Remove the player from the mejs.players object so that pauseOtherPlayers doesn't blow up when trying to pause a non existance flash api. + delete mejs.players[t.id]; + + if (typeof t.container == 'object') { + t.container.remove(); + } + t.globalUnbind(); + delete t.node.player; + } + }; + + (function(){ + var rwindow = /^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/; + + function splitEvents(events, id) { + // add player ID as an event namespace so it's easier to unbind them all later + var ret = {d: [], w: []}; + $.each((events || '').split(' '), function(k, v){ + var eventname = v + '.' + id; + if (eventname.indexOf('.') === 0) { + ret.d.push(eventname); + ret.w.push(eventname); + } + else { + ret[rwindow.test(v) ? 'w' : 'd'].push(eventname); + } + }); + ret.d = ret.d.join(' '); + ret.w = ret.w.join(' '); + return ret; + } + + mejs.MediaElementPlayer.prototype.globalBind = function(events, data, callback) { + var t = this; + events = splitEvents(events, t.id); + if (events.d) $(document).bind(events.d, data, callback); + if (events.w) $(window).bind(events.w, data, callback); + }; + + mejs.MediaElementPlayer.prototype.globalUnbind = function(events, callback) { + var t = this; + events = splitEvents(events, t.id); + if (events.d) $(document).unbind(events.d, callback); + if (events.w) $(window).unbind(events.w, callback); + }; + })(); + + // turn into jQuery plugin + if (typeof $ != 'undefined') { + $.fn.mediaelementplayer = function (options) { + if (options === false) { + this.each(function () { + var player = $(this).data('mediaelementplayer'); + if (player) { + player.remove(); + } + $(this).removeData('mediaelementplayer'); + }); + } + else { + this.each(function () { + $(this).data('mediaelementplayer', new mejs.MediaElementPlayer(this, options)); + }); + } + return this; + }; + + + $(document).ready(function() { + // auto enable using JSON attribute + $('.mejs-player').mediaelementplayer(); + }); + } + + // push out to window + window.MediaElementPlayer = mejs.MediaElementPlayer; + +})(mejs.$); diff --git a/js/mediaelement/src/silverlight/App.xaml b/js/mediaelement/src/silverlight/App.xaml new file mode 100644 index 0000000000000000000000000000000000000000..47cf6fb13f6e65800968411ca3af29a81bd58fbc --- /dev/null +++ b/js/mediaelement/src/silverlight/App.xaml @@ -0,0 +1,8 @@ +<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="SilverlightMediaElement.App" + > + <Application.Resources> + + </Application.Resources> +</Application> diff --git a/js/mediaelement/src/silverlight/App.xaml.cs b/js/mediaelement/src/silverlight/App.xaml.cs new file mode 100644 index 0000000000000000000000000000000000000000..be67bfbf7b3748ccd7ad0f1cf3ff8559049d5024 --- /dev/null +++ b/js/mediaelement/src/silverlight/App.xaml.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; + +namespace SilverlightMediaElement +{ + public partial class App : Application + { + + public App() + { + this.Startup += this.Application_Startup; + this.Exit += this.Application_Exit; + this.UnhandledException += this.Application_UnhandledException; + + InitializeComponent(); + } + + private void Application_Startup(object sender, StartupEventArgs e) + { + this.RootVisual = new MainPage(e.InitParams); + } + + private void Application_Exit(object sender, EventArgs e) + { + + } + + private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) + { + // If the app is running outside of the debugger then report the exception using + // the browser's exception mechanism. On IE this will display it a yellow alert + // icon in the status bar and Firefox will display a script error. + if (!System.Diagnostics.Debugger.IsAttached) + { + + // NOTE: This will allow the application to continue running after an exception has been thrown + // but not handled. + // For production applications this error handling should be replaced with something that will + // report the error to the website and stop the application. + e.Handled = true; + Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); }); + } + } + + private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e) + { + try + { + string errorMsg = e.ExceptionObject.Message + e.ExceptionObject.StackTrace; + errorMsg = errorMsg.Replace('"', '\'').Replace("\r\n", @"\n"); + + System.Windows.Browser.HtmlPage.Window.Eval("throw new Error(\"Unhandled Error in Silverlight Application " + errorMsg + "\");"); + } + catch (Exception) + { + } + } + } +} diff --git a/js/mediaelement/src/silverlight/MainPage.xaml b/js/mediaelement/src/silverlight/MainPage.xaml new file mode 100644 index 0000000000000000000000000000000000000000..54af028fe5bf46abc76ec1d833f6dc4f6698feaf --- /dev/null +++ b/js/mediaelement/src/silverlight/MainPage.xaml @@ -0,0 +1,276 @@ + +<UserControl + x:Class="SilverlightMediaElement.MainPage" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" + mc:Ignorable="d" + Width="640" Height="360"> + <UserControl.Resources> + <Style x:Key="roundThumbStyle" TargetType="Thumb"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="Thumb"> + <Ellipse Stroke="#FFFFFFFF" StrokeThickness="2" Fill="#FF484848"/> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="SliderStyle" TargetType="Slider"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="Slider"> + <Grid x:Name="Root" Background="Transparent"> + <Grid.Resources> + <ControlTemplate x:Key="RightRepeatButtonTemplate"> + <Rectangle Height="8" Margin="-5,0,0,0" Grid.Column="0" Grid.ColumnSpan="3" + StrokeThickness="0.5" RadiusY="1" RadiusX="1" Fill="#FF484848"/> + </ControlTemplate> + <ControlTemplate x:Key="LeftRepeatButtonTemplate"> + <Rectangle Height="8" Margin="0,0,-5,0" Grid.Column="0" Grid.ColumnSpan="3" + StrokeThickness="0.5" RadiusY="1" RadiusX="1" Fill="#FFAFAFAF"/> + </ControlTemplate> + </Grid.Resources> + <Grid x:Name="HorizontalTemplate"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*"/> + </Grid.ColumnDefinitions> + + <RepeatButton x:Name="HorizontalTrackLargeChangeDecreaseRepeatButton" Grid.Column="0" + IsTabStop="False" Template="{StaticResource LeftRepeatButtonTemplate}"/> + <Rectangle x:Name="LeftTrack" Grid.Row="1" Fill="#00FFFFFF" Cursor="Hand" MouseLeftButtonDown="LeftTrack_MouseLeftButtonDown"/> + <Thumb Background="#00FFFFFF" Height="10" x:Name="HorizontalThumb" Width="10" + Grid.Column="1" Style="{StaticResource roundThumbStyle}" HorizontalAlignment="Left" + DragStarted="HorizontalThumb_DragStarted" DragCompleted="HorizontalThumb_DragCompleted" + Canvas.ZIndex="1"/> + <RepeatButton x:Name="HorizontalTrackLargeChangeIncreaseRepeatButton" Grid.Column="2" + IsTabStop="False" Template="{StaticResource RightRepeatButtonTemplate}"/> + <Rectangle x:Name="RightTrack" Grid.Column="2" Grid.Row="1" Fill="#00FFFFFF" Cursor="Hand" MouseLeftButtonDown="LeftTrack_MouseLeftButtonDown"/> + </Grid> + </Grid> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <ControlTemplate x:Key="PlayButtonTemplate" TargetType="ToggleButton"> + <Grid x:Name="grid" Background="Transparent"> + <vsm:VisualStateManager.VisualStateGroups> + <vsm:VisualStateGroup x:Name="FocusStates"> + <vsm:VisualState x:Name="Focused"> + <Storyboard> + </Storyboard> + </vsm:VisualState> + <vsm:VisualState x:Name="Unfocused"> + <Storyboard/> + </vsm:VisualState> + </vsm:VisualStateGroup> + <vsm:VisualStateGroup x:Name="CommonStates"> + <vsm:VisualState x:Name="Normal"> + <Storyboard/> + </vsm:VisualState> + <vsm:VisualState x:Name="MouseOver"> + <Storyboard> + </Storyboard> + </vsm:VisualState> + <vsm:VisualState x:Name="Pressed"> + <Storyboard/> + </vsm:VisualState> + </vsm:VisualStateGroup> + <vsm:VisualStateGroup x:Name="CheckStates"> + <vsm:VisualState x:Name="Checked"> + <Storyboard> + <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" + Storyboard.TargetName="playSymbol" + Storyboard.TargetProperty="(UIElement.Opacity)"> + <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/> + </DoubleAnimationUsingKeyFrames> + <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" + Storyboard.TargetName="pauseSymbol" + Storyboard.TargetProperty="(UIElement.Opacity)"> + <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </vsm:VisualState> + <vsm:VisualState x:Name="Unchecked"> + <Storyboard> + <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="pauseSymbol" Storyboard.TargetProperty="(UIElement.Opacity)"> + <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </vsm:VisualState> + </vsm:VisualStateGroup> + </vsm:VisualStateManager.VisualStateGroups> + <Grid Margin="11,3,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" + Opacity="1" x:Name="playSymbol" Width="21" Height="22"> + <Path Width="14" Height="15" Stretch="Fill" Fill="#FF7F7F7F" + Data="F1 M 15.1997,22.542L 29.7776,14.89L 15.2707,6.99886L 15.1997,22.542 Z "/> + </Grid> + <Grid Margin="11,2,0,0" Opacity="0" x:Name="pauseSymbol" Width="31" Height="15"> + <Rectangle Stretch="Fill" Fill="#FF7F7F7F" HorizontalAlignment="Left" + Margin="0,0,0,0" Width="6"/> + <Rectangle Stretch="Fill" Fill="#FF7F7F7F" HorizontalAlignment="Stretch" + Margin="6,0,13,0" Width="6"/> + </Grid> + </Grid> + </ControlTemplate> + + <ControlTemplate x:Key="MuteButtonTemplate" TargetType="ToggleButton"> + <Grid Background="Transparent" Cursor="Hand"> + <vsm:VisualStateManager.VisualStateGroups> + <vsm:VisualStateGroup x:Name="FocusStates"> + <vsm:VisualState x:Name="Focused"> + <Storyboard> + </Storyboard> + </vsm:VisualState> + <vsm:VisualState x:Name="Unfocused"> + <Storyboard/> + </vsm:VisualState> + </vsm:VisualStateGroup> + <vsm:VisualStateGroup x:Name="CommonStates"> + <vsm:VisualState x:Name="Normal"> + <Storyboard/> + </vsm:VisualState> + <vsm:VisualState x:Name="MouseOver"> + <Storyboard> + </Storyboard> + </vsm:VisualState> + <vsm:VisualState x:Name="Pressed"> + <Storyboard/> + </vsm:VisualState> + </vsm:VisualStateGroup> + <vsm:VisualStateGroup x:Name="CheckStates"> + <vsm:VisualState x:Name="Checked"> + <Storyboard> + <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" + Storyboard.TargetName="volumeSymbol" + Storyboard.TargetProperty="(UIElement.Opacity)"> + <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </vsm:VisualState> + <vsm:VisualState x:Name="Unchecked"> + <Storyboard> + <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" + Storyboard.TargetName="volumeSymbol" + Storyboard.TargetProperty="(UIElement.Opacity)"> + <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </vsm:VisualState> + </vsm:VisualStateGroup> + </vsm:VisualStateManager.VisualStateGroups> + <Grid HorizontalAlignment="Left" VerticalAlignment="Top" + Width="17"> + <Path HorizontalAlignment="Left" Stretch="Fill" + Fill="#FF7F7F7F" Data="F1 M 23.1457,26.5056L 23.1457,33.8944L 25.7913,33.8944L 28.8235,37.4722L 30.5346,37.4722L 30.5665,23.0833L 28.8995,23.0833L 25.8679,26.5056L 23.1457,26.5056 Z " + Width="7.421" Height="14.389" UseLayoutRounding="False" + Margin="0,6.5,0,6.5"/> + + <Grid HorizontalAlignment="Right" Width="7.003" x:Name="volumeSymbol" Margin="0,10"> + <Path HorizontalAlignment="Right" VerticalAlignment="Stretch" + Width="2.398" + Data="M0.5,0.5 C0.5,0.5 2.5939558,2.7128265 2.5946648,7.0504856 C2.5953746,11.391507 0.50033337,13.889001 0.50033337,13.889001" + Stretch="Fill" Stroke="#FF7F7F7F" Margin="0,0,-0.398,0" UseLayoutRounding="False"/> + <Path HorizontalAlignment="Stretch" Margin="2.4,2.384,2.317,1.584" VerticalAlignment="Stretch" + Data="M0.5,0.50000006 C0.5,0.50000006 1.4786903,2.1275051 1.4781417,4.9569001 C1.4776551,7.4670725 0.35717732,9.892808 0.35717732,9.892808" Stretch="Fill" Stroke="#FF7F7F7F" UseLayoutRounding="False"/> + <Path HorizontalAlignment="Left" Margin="0,4.36,0,3.46" VerticalAlignment="Stretch" Width="1.542" + Data="M0.5,0.5 C0.5,0.5 1.0412779,1.4903735 1.042276,3.1459465 C1.0429831,4.3189368 0.66544437,6.0685911 0.66544437,6.0685911" Stretch="Fill" Stroke="#FF7F7F7F" d:LayoutOverrides="Width"/> + </Grid> + </Grid> + </Grid> + </ControlTemplate> + + <ControlTemplate x:Key="ButtonTemplate" TargetType="Button"> + <Grid Background="Transparent"> + <ContentPresenter Width="Auto"/> + </Grid> + </ControlTemplate> + </UserControl.Resources> + + <Grid x:Name="LayoutRoot" Background="Black" Width="640" Height="360"> + + <MediaElement Grid.Row="0" Grid.Column="0" Width="640" Height="360" + CacheMode="BitmapCache" AutoPlay="false" Name="media"> + + </MediaElement> + <Button x:Name="bigPlayButton" Template="{StaticResource ButtonTemplate}" + Click="BigPlayButton_Click" Grid.Row="0" Visibility="Collapsed"> + <Canvas Width="100" Height="100"> + <Path Width="100" Height="100" Canvas.Left="0" Canvas.Top="0" Stretch="Fill" + Fill="#77000000" Data="F1 M 15,0L 85,0C 93.2843,0 100,6.71573 100,15L 100,85C 100,93.2843 93.2843,100 85,100L 15,100C 6.71573,100 0,93.2843 0,85L 0,15C 0,6.71573 6.71573,0 15,0 Z "/> + <Path Width="40.8182" Height="47.1328" Canvas.Left="34.6439" + Canvas.Top="27.6003" Stretch="Fill" Fill="#FFFFFFFF" + Data="F1 M 75.4621,51.1667L 34.6439,27.6003L 34.6439,74.7331L 75.4621,51.1667 Z "/> + </Canvas> + </Button> + <TextBox Margin="25,25,0,0" Name="textBox1" VerticalScrollBarVisibility="Auto" Height="146" VerticalAlignment="Top" HorizontalAlignment="Left" Width="235" /> + <Button Content="" Height="36" HorizontalAlignment="Right" x:Name="FullscreenButton" VerticalAlignment="Bottom" Width="31" Click="FullscreenButton_Click" Opacity="0" Background="#00000000" Cursor="Hand" /> + + <Grid x:Name="transportControls" VerticalAlignment="Bottom" Height="40" Background="#FF000000" + Grid.Row="1" > + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="0" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" MinWidth="10" /> + <ColumnDefinition Width="Auto" MinWidth="10" /> + </Grid.ColumnDefinitions> + + <!-- play symbol showing is checked = false, Pause symbol showing is checked = true--> + <ToggleButton x:Name="playPauseButton" Template="{StaticResource PlayButtonTemplate}" + Click="PlayPauseButton_Click" IsChecked="false" Cursor="Hand"/> + + <Grid x:Name="time" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="40" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="10" /> + <ColumnDefinition Width="40" /> + </Grid.ColumnDefinitions> + + <TextBlock x:Name="currentTimeTextBlock" Margin="0,1.5,10,0" Height="12" + FontFamily="Verdana" FontSize="10" Text="00:00" TextWrapping="Wrap" + Foreground="#FFFFFFFF" FontStyle="Normal" HorizontalAlignment="Right" + TextAlignment="Right" Grid.Column="0"/> + + <Slider x:Name="timelineSlider" Margin="0,2,0,-1" + Maximum="1" Style="{StaticResource SliderStyle}" Grid.Column="1" + ValueChanged="TimelineSlider_ValueChanged" + Value="0"/> + + <TextBlock Margin="0,1.5,0,0" Height="12" FontFamily="Verdana" FontSize="10" + Text="" TextWrapping="Wrap" Foreground="#FFFFFFFF" + FontStyle="Normal" HorizontalAlignment="Center" TextAlignment="Right" + Grid.Column="2"/> + <TextBlock x:Name="totalTimeTextBlock" Margin="0,1.5,0,0" Height="12" + FontFamily="Verdana" FontSize="10" Text="00:00" TextWrapping="Wrap" + Foreground="#FFFFFFFF" FontStyle="Normal" HorizontalAlignment="Left" + TextAlignment="Right" Grid.Column="3"/> + </Grid> + + <ToggleButton IsChecked="false" Grid.Column="3" x:Name="muteButton" + Template="{StaticResource MuteButtonTemplate}" Click="MuteButton_Click" + VerticalAlignment="Center" Margin="0,0,6,0" Cursor="Hand"/> + + <Slider Grid.Column="4" HorizontalAlignment="Stretch" Margin="3,0,0,0" + VerticalAlignment="Center" Maximum="1" x:Name="volumeSlider" + Background="#FF777777" Style="{StaticResource SliderStyle}" Width="50" + Value="{Binding ElementName=media, Mode=TwoWay, Path=Volume, UpdateSourceTrigger=Default}"/> + + <Button x:Name="fullScreenButton" Grid.Column="5" Margin="8,10,4,10" Click="FullScreenButton_Click" + Template="{StaticResource ButtonTemplate}" VerticalAlignment="Center" Cursor="Hand"> + <Path Height="14.375" HorizontalAlignment="Stretch" + VerticalAlignment="Bottom" RenderTransformOrigin="0.212389379739761,0.208695650100708" + Data="M10.181361,8.375 L12.844413,11.008244 L14.125,9.7418737 L14.125,14.375 L9.675765,14.374833 L10.906104,13.158273 L8.125,10.408315 L10.181361,8.375 z M3.9666855,8.375 L6,10.431361 L3.3667567,13.094413 L4.6331258,14.375 L0,14.375 L0.00016707927,9.925765 L1.2167276,11.156104 L3.9666855,8.375 z M9.4918737,0 L14.125,0 L14.124833,4.449235 L12.908273,3.2188957 L10.158315,6 L8.125,3.943639 L10.758244,1.2805867 L9.4918737,0 z M0,0 L4.449235,0.00016686507 L3.2188957,1.2167276 L6,3.9666855 L3.943639,6 L1.280587,3.3667567 L0,4.6331258 L0,0 z" + Fill="#FF7F7F7F" Stretch="Fill" Stroke="#FF000000" StrokeThickness="0" /> + </Button> + </Grid> + </Grid> +</UserControl> \ No newline at end of file diff --git a/js/mediaelement/src/silverlight/MainPage.xaml.cs b/js/mediaelement/src/silverlight/MainPage.xaml.cs new file mode 100644 index 0000000000000000000000000000000000000000..5f5da703f949ce8452e686be9238fd53550a7281 --- /dev/null +++ b/js/mediaelement/src/silverlight/MainPage.xaml.cs @@ -0,0 +1,636 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using System.Windows.Browser; +using System.Globalization; + + +namespace SilverlightMediaElement +{ + [ScriptableType] + public partial class MainPage : UserControl + { + System.Windows.Threading.DispatcherTimer _timer; + + // work arounds for src, load(), play() compatibility + bool _isLoading = false; + bool _isAttemptingToPlay = false; + + // variables + string _mediaUrl; + string _preload; + string _htmlid; + bool _autoplay = false; + bool _debug = false; + int _width = 0; + int _height = 0; + int _timerRate = 0; + double _bufferedBytes = 0; + double _bufferedTime = 0; + double _volume = 1; + int _videoWidth = 0; + int _videoHeight = 0; + + // state + bool _isPaused = true; + bool _isEnded = false; + + // dummy + bool _firedCanPlay = false; + + // mediaElement.Position updates TimelineSlider.Value, and + // updating TimelineSlider.Value updates mediaElement.Position, + // this variable helps us break the infinite loop + private bool duringTickEvent = false; + + private bool playVideoWhenSliderDragIsOver = false; + + public MainPage(IDictionary<string, string> initParams) + { + InitializeComponent(); + + HtmlPage.RegisterScriptableObject("MediaElementJS", this); + + + // add events + media.BufferingProgressChanged += new RoutedEventHandler(media_BufferingProgressChanged); + media.DownloadProgressChanged += new RoutedEventHandler(media_DownloadProgressChanged); + media.CurrentStateChanged += new RoutedEventHandler(media_CurrentStateChanged); + media.MediaEnded += new RoutedEventHandler(media_MediaEnded); + media.MediaFailed += new EventHandler<ExceptionRoutedEventArgs>(media_MediaFailed); + media.MediaOpened += new RoutedEventHandler(media_MediaOpened); + media.MouseLeftButtonDown += new MouseButtonEventHandler(media_MouseLeftButtonDown); + CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); + transportControls.Visibility = System.Windows.Visibility.Collapsed; + + // get parameters + if (initParams.ContainsKey("id")) + _htmlid = initParams["id"]; + if (initParams.ContainsKey("file")) + _mediaUrl = initParams["file"]; + if (initParams.ContainsKey("autoplay") && initParams["autoplay"] == "true") + _autoplay = true; + if (initParams.ContainsKey("debug") && initParams["debug"] == "true") + _debug = true; + if (initParams.ContainsKey("preload")) + _preload = initParams["preload"].ToLower(); + else + _preload = ""; + + if (!(new string[] { "none", "metadata", "auto" }).Contains(_preload)){ + _preload = "none"; + } + + if (initParams.ContainsKey("width")) + Int32.TryParse(initParams["width"], out _width); + if (initParams.ContainsKey("height")) + Int32.TryParse(initParams["height"], out _height); + if (initParams.ContainsKey("timerate")) + Int32.TryParse(initParams["timerrate"], out _timerRate); + if (initParams.ContainsKey("startvolume")) + Double.TryParse(initParams["startvolume"], out _volume); + + if (_timerRate == 0) + _timerRate = 250; + + // timer + _timer = new System.Windows.Threading.DispatcherTimer(); + _timer.Interval = new TimeSpan(0, 0, 0, 0, _timerRate); // 200 Milliseconds + _timer.Tick += new EventHandler(timer_Tick); + _timer.Stop(); + + //_mediaUrl = "http://local.mediaelement.com/media/jsaddington.mp4"; + //_autoplay = true; + + // set stage and media sizes + if (_width > 0) + LayoutRoot.Width = media.Width = this.Width = _width; + if (_height > 0) + LayoutRoot.Height = media.Height = this.Height = _height; + + // debug + textBox1.Visibility = (_debug) ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed; + textBox1.IsEnabled = false; + textBox1.Text = "id: " + _htmlid + "\n" + + "file: " + _mediaUrl + "\n"; + + + media.AutoPlay = _autoplay; + media.Volume = _volume; + if (!String.IsNullOrEmpty(_mediaUrl)) { + setSrc(_mediaUrl); + if (_autoplay || _preload != "none") + loadMedia(); + } + + media.MouseLeftButtonUp += new MouseButtonEventHandler(media_MouseLeftButtonUp); + + // full screen settings + Application.Current.Host.Content.FullScreenChanged += new EventHandler(DisplaySizeInformation); + Application.Current.Host.Content.Resized += new EventHandler(DisplaySizeInformation); + //FullscreenButton.Visibility = System.Windows.Visibility.Collapsed; + + // send out init call + //HtmlPage.Window.Invoke("html5_MediaPluginBridge_initPlugin", new object[] {_htmlid}); + try + { + HtmlPage.Window.Eval("mejs.MediaPluginBridge.initPlugin('" + _htmlid + "');"); + } + catch { } + } + + void media_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + switch (media.CurrentState) + { + case MediaElementState.Playing: + pauseMedia(); + break; + + case MediaElementState.Paused: + playMedia(); + break; + case MediaElementState.Stopped: + + break; + case MediaElementState.Buffering: + pauseMedia(); + break; + } + } + + void media_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { + SendEvent("click"); + } + + void media_MediaOpened(object sender, RoutedEventArgs e) { + + _videoWidth = Convert.ToInt32(media.NaturalVideoWidth); + _videoHeight = Convert.ToInt32(media.NaturalVideoHeight); + + TimeSpan duration = media.NaturalDuration.TimeSpan; + totalTimeTextBlock.Text = TimeSpanToString(duration); + UpdateVideoSize(); + + playPauseButton.IsChecked = true; + + SendEvent("loadedmetadata"); + } + + void timer_Tick(object sender, EventArgs e) { + SendEvent("timeupdate"); + } + + void StartTimer() { + _timer.Start(); + } + + void StopTimer() { + _timer.Stop(); + } + + void WriteDebug(string text) { + textBox1.Text += text + "\n"; + } + + void media_MediaFailed(object sender, ExceptionRoutedEventArgs e) { + SendEvent(e.ToString()); + } + + void media_MediaEnded(object sender, RoutedEventArgs e) { + _isEnded = true; + _isPaused = false; + SendEvent("ended"); + StopTimer(); + } + + void media_CurrentStateChanged(object sender, RoutedEventArgs e) { + + WriteDebug("state:" + media.CurrentState.ToString()); + + switch (media.CurrentState) + { + case MediaElementState.Opening: + SendEvent("loadstart"); + break; + case MediaElementState.Playing: + _isEnded = false; + _isPaused = false; + _isAttemptingToPlay = false; + StartTimer(); + + + SendEvent("play"); + SendEvent("playing"); + break; + + case MediaElementState.Paused: + _isEnded = false; + _isPaused = true; + + // special settings to allow play() to work + _isLoading = false; + WriteDebug("paused event, " + _isAttemptingToPlay); + if (_isAttemptingToPlay) { + this.playMedia(); + } + + StopTimer(); + SendEvent("paused"); + break; + case MediaElementState.Stopped: + _isEnded = false; + _isPaused = true; + StopTimer(); + SendEvent("paused"); + break; + case MediaElementState.Buffering: + SendEvent("progress"); + break; + } + + } + + void media_BufferingProgressChanged(object sender, RoutedEventArgs e) { + _bufferedTime = media.DownloadProgress * media.NaturalDuration.TimeSpan.TotalSeconds; + _bufferedBytes = media.BufferingProgress; + + + SendEvent("progress"); + } + + void media_DownloadProgressChanged(object sender, RoutedEventArgs e) { + _bufferedTime = media.DownloadProgress * media.NaturalDuration.TimeSpan.TotalSeconds; + _bufferedBytes = media.BufferingProgress; + + if (!_firedCanPlay) { + SendEvent("loadeddata"); + SendEvent("canplay"); + _firedCanPlay = true; + } + + SendEvent("progress"); + } + + + void SendEvent(string name) { + /* + * INVOKE + HtmlPage.Window.Invoke("html5_MediaPluginBridge_fireEvent", + _htmlid, + name, + @"'{" + + @"""name"": """ + name + @"""" + + @", ""currentTime"":" + (media.Position.TotalSeconds).ToString() + @"" + + @", ""duration"":" + (media.NaturalDuration.TimeSpan.TotalSeconds).ToString() + @"" + + @", ""paused"":" + (_isEnded).ToString().ToLower() + @"" + + @", ""muted"":" + (media.IsMuted).ToString().ToLower() + @"" + + @", ""ended"":" + (_isPaused).ToString().ToLower() + @"" + + @", ""volume"":" + (media.Volume).ToString() + @"" + + @", ""bufferedBytes"":" + (_bufferedBytes).ToString() + @"" + + @", ""bufferedTime"":" + (_bufferedTime).ToString() + @"" + + @"}'"); + */ + + /* + * EVAL + HtmlPage.Window.Eval("mejs.MediaPluginBridge.fireEvent('" + _htmlid + "','" + name + "'," + + @"{" + + @"""name"": """ + name + @"""" + + @", ""currentTime"":" + (media.Position.TotalSeconds).ToString() + @"" + + @", ""duration"":" + (media.NaturalDuration.TimeSpan.TotalSeconds).ToString() + @"" + + @", ""paused"":" + (_isEnded).ToString().ToLower() + @"" + + @", ""muted"":" + (media.IsMuted).ToString().ToLower() + @"" + + @", ""ended"":" + (_isPaused).ToString().ToLower() + @"" + + @", ""volume"":" + (media.Volume).ToString() + @"" + + @", ""bufferedBytes"":" + (_bufferedBytes).ToString() + @"" + + @", ""bufferedTime"":" + (_bufferedTime).ToString() + @"" + + @"});"); + * */ + + // setTimeout + try { + CultureInfo invCulture = CultureInfo.InvariantCulture; + + HtmlPage.Window.Invoke("setTimeout", "mejs.MediaPluginBridge.fireEvent('" + _htmlid + "','" + name + "'," + + @"{" + + @"""name"": """ + name + @"""" + + @", ""currentTime"":" + (media.Position.TotalSeconds).ToString(invCulture) + @"" + + @", ""duration"":" + (media.NaturalDuration.TimeSpan.TotalSeconds).ToString(invCulture) + @"" + + @", ""paused"":" + (_isPaused).ToString().ToLower() + @"" + + @", ""muted"":" + (media.IsMuted).ToString().ToLower() + @"" + + @", ""ended"":" + (_isEnded).ToString().ToLower() + @"" + + @", ""volume"":" + (media.Volume).ToString(invCulture) + @"" + + @", ""bufferedBytes"":" + (_bufferedBytes).ToString(invCulture) + @"" + + @", ""bufferedTime"":" + (_bufferedTime).ToString(invCulture) + @"" + + @", ""videoWidth"":" + (_videoWidth).ToString() + @"" + + @", ""videoHeight"":" + (_videoHeight).ToString() + @"" + + @"});", 0); + } catch { } + + } + + /* HTML5 wrapper methods */ + [ScriptableMember] + public void playMedia() { + WriteDebug("method:play " + media.CurrentState); + + // sometimes people forget to call load() first + if (_mediaUrl != "" && media.Source == null) { + _isAttemptingToPlay = true; + loadMedia(); + } + + // store and trigger with the state change above + if (media.CurrentState == MediaElementState.Closed && _isLoading) { + WriteDebug("storing _isAttemptingToPlay "); + _isAttemptingToPlay = true; + } + + media.Play(); + _isEnded = false; + _isPaused = false; + + playPauseButton.IsChecked = true; + + //StartTimer(); + } + + [ScriptableMember] + public void pauseMedia() { + WriteDebug("method:pause " + media.CurrentState); + + _isEnded = false; + _isPaused = true; + + media.Pause(); + StopTimer(); + playPauseButton.IsChecked = false; + } + + [ScriptableMember] + public void loadMedia() { + _isLoading = true; + _firedCanPlay = false; + + WriteDebug("method:load " + media.CurrentState); + WriteDebug(" - " + _mediaUrl.ToString()); + + media.Source = new Uri(_mediaUrl, UriKind.Absolute); + //media.Play(); + //media.Stop(); + } + + [ScriptableMember] + public void stopMedia() { + WriteDebug("method:stop " + media.CurrentState); + + _isEnded = true; + _isPaused = false; + + media.Stop(); + StopTimer(); + playPauseButton.IsChecked = false; + } + + [ScriptableMember] + public void setVolume(Double volume) { + WriteDebug("method:setvolume: " + volume.ToString()); + + media.Volume = volume; + + SendEvent("volumechange"); + } + + [ScriptableMember] + public void setMuted(bool isMuted) { + WriteDebug("method:setmuted: " + isMuted.ToString()); + + media.IsMuted = isMuted; + muteButton.IsChecked = isMuted; + SendEvent("volumechange"); + + } + + [ScriptableMember] + public void setCurrentTime(Double position) { + WriteDebug("method:setCurrentTime: " + position.ToString()); + + int milliseconds = Convert.ToInt32(position * 1000); + + SendEvent("seeking"); + media.Position = new TimeSpan(0, 0, 0, 0, milliseconds); + SendEvent("seeked"); + } + + [ScriptableMember] + public void setSrc(string url) { + _mediaUrl = url; + } + + [ScriptableMember] + public void setFullscreen(bool goFullscreen) { + + FullscreenButton.Visibility = System.Windows.Visibility.Visible; + } + + [ScriptableMember] + public void setVideoSize(int width, int height) { + this.Width = media.Width = width; + this.Height = media.Height = height; + } + + [ScriptableMember] + public void positionFullscreenButton(int x, int y,bool visibleAndAbove) { + if (visibleAndAbove) + { + //FullscreenButton.Visibility = System.Windows.Visibility.Collapsed; + } + else + { + //FullscreenButton.Visibility = System.Windows.Visibility.Visible; + } + } + + private void FullscreenButton_Click(object sender, RoutedEventArgs e) { + Application.Current.Host.Content.IsFullScreen = true; + //FullscreenButton.Visibility = System.Windows.Visibility.Collapsed; + } + + private void DisplaySizeInformation(Object sender, EventArgs e) { + this.Width = LayoutRoot.Width = media.Width = Application.Current.Host.Content.ActualWidth; + this.Height = LayoutRoot.Height = media.Height = Application.Current.Host.Content.ActualHeight; + + UpdateVideoSize(); + } + + + + + #region play button + + private void BigPlayButton_Click(object sender, RoutedEventArgs e) + { + playPauseButton.IsChecked = true; + PlayPauseButton_Click(sender, e); + } + + private void PlayPauseButton_Click(object sender, RoutedEventArgs e) + { + bigPlayButton.Visibility = Visibility.Collapsed; + + // this will be the toggle button state after the click has been processed + if (playPauseButton.IsChecked == true) + playMedia(); + else + pauseMedia(); + } + + + + #endregion + + #region timelineSlider + + private void Seek(double percentComplete) + { + if (duringTickEvent) + throw new Exception("Can't call Seek() now, you'll get an infinite loop"); + + TimeSpan duration = media.NaturalDuration.TimeSpan; + int newPosition = (int)(duration.TotalSeconds * percentComplete); + media.Position = new TimeSpan(0, 0, newPosition); + + // let the next CompositionTarget.Rendering take care of updating the text blocks + } + + private Slider GetSliderParent(object sender) + { + FrameworkElement element = (FrameworkElement)sender; + do + { + element = (FrameworkElement)VisualTreeHelper.GetParent(element); + } while (!(element is Slider)); + return (Slider)element; + } + + private void LeftTrack_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + e.Handled = true; + FrameworkElement lefttrack = (sender as FrameworkElement).FindName("LeftTrack") as FrameworkElement; + FrameworkElement righttrack = (sender as FrameworkElement).FindName("RightTrack") as FrameworkElement; + double position = e.GetPosition(lefttrack).X; + double width = righttrack.TransformToVisual(lefttrack).Transform(new Point(righttrack.ActualWidth, righttrack.ActualHeight)).X; + double percent = position / width; + Slider slider = GetSliderParent(sender); + slider.Value = percent; + } + + private void HorizontalThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e) + { + if (GetSliderParent(sender) != timelineSlider) return; + + bool notPlaying = (media.CurrentState == MediaElementState.Paused + || media.CurrentState == MediaElementState.Stopped); + + if (notPlaying) + { + playVideoWhenSliderDragIsOver = false; + } + else + { + playVideoWhenSliderDragIsOver = true; + media.Pause(); + } + } + + private void HorizontalThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e) + { + if (playVideoWhenSliderDragIsOver) + media.Play(); + } + + private void TimelineSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) + { + if (duringTickEvent) + return; + + Seek(timelineSlider.Value); + } + + #endregion + + #region updating current time + + private void CompositionTarget_Rendering(object sender, EventArgs e) + { + duringTickEvent = true; + + TimeSpan duration = media.NaturalDuration.TimeSpan; + if (duration.TotalSeconds != 0) + { + double percentComplete = (media.Position.TotalSeconds / duration.TotalSeconds); + timelineSlider.Value = percentComplete; + string text = TimeSpanToString(media.Position); + if (this.currentTimeTextBlock.Text != text) + this.currentTimeTextBlock.Text = text; + } + + duringTickEvent = false; + } + + private static string TimeSpanToString(TimeSpan time) + { + return string.Format("{0:00}:{1:00}", (time.Hours * 60) + time.Minutes, time.Seconds); + } + #endregion + + private void MuteButton_Click(object sender, RoutedEventArgs e) + { + //media.IsMuted = (bool)muteButton.IsChecked; + setMuted((bool)muteButton.IsChecked); + } + + #region fullscreen mode + + private void FullScreenButton_Click(object sender, RoutedEventArgs e) + { + var content = Application.Current.Host.Content; + content.IsFullScreen = !content.IsFullScreen; + } + + private void Content_FullScreenChanged(object sender, EventArgs e) + { + UpdateVideoSize(); + } + + private void UpdateVideoSize() + { + if (App.Current.Host.Content.IsFullScreen) + { + transportControls.Visibility = System.Windows.Visibility.Visible; + // mediaElement takes all available space + //VideoRow.Height = new GridLength(1, GridUnitType.Star); + //VideoColumn.Width = new GridLength(1, GridUnitType.Star); + } + else + { + transportControls.Visibility = System.Windows.Visibility.Collapsed; + // mediaElement is only as big as the source video + //VideoRow.Height = new GridLength(1, GridUnitType.Auto); + //VideoColumn.Width = new GridLength(1, GridUnitType.Auto); + } + } + + #endregion + } +} + diff --git a/js/mediaelement/src/silverlight/Properties/AppManifest.xml b/js/mediaelement/src/silverlight/Properties/AppManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..6712a117839325b3d857001847396798160b3a6f --- /dev/null +++ b/js/mediaelement/src/silverlight/Properties/AppManifest.xml @@ -0,0 +1,6 @@ +<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" +> + <Deployment.Parts> + </Deployment.Parts> +</Deployment> diff --git a/js/mediaelement/src/silverlight/Properties/AssemblyInfo.cs b/js/mediaelement/src/silverlight/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..8d8bbce31418d7600de87584ab5362f4956656d3 --- /dev/null +++ b/js/mediaelement/src/silverlight/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SilverlightMediaElement")] +[assembly: AssemblyDescription("Silverlight player for http://mediaelementjs.com/")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("John Dyer")] +[assembly: AssemblyProduct("SilverlightMediaElement")] +[assembly: AssemblyCopyright("Copyright 2010 John Dyer")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("29fe7094-d10f-4359-8abb-1c76971133a4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/js/mediaelement/src/silverlight/SilverlightMediaElement.csproj b/js/mediaelement/src/silverlight/SilverlightMediaElement.csproj new file mode 100644 index 0000000000000000000000000000000000000000..5658304609eb248e9fe5336d1fa7f8961221c684 --- /dev/null +++ b/js/mediaelement/src/silverlight/SilverlightMediaElement.csproj @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>8.0.50727</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{CE832DD6-B659-4F3E-B49B-F297E2AC923A}</ProjectGuid> + <ProjectTypeGuids>{A1591282-1198-4647-A2B1-27E5FF5F6F3B};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>SilverlightMediaElement</RootNamespace> + <AssemblyName>SilverlightMediaElement</AssemblyName> + <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier> + <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> + <SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion> + <SilverlightApplication>true</SilverlightApplication> + <SupportedCultures> + </SupportedCultures> + <XapOutputs>true</XapOutputs> + <GenerateSilverlightManifest>true</GenerateSilverlightManifest> + <XapFilename>silverlightmediaelement.xap</XapFilename> + <SilverlightManifestTemplate>Properties\AppManifest.xml</SilverlightManifestTemplate> + <SilverlightAppEntry>SilverlightMediaElement.App</SilverlightAppEntry> + <TestPageFileName>SilverlightMediaElementTestPage.html</TestPageFileName> + <CreateTestPage>true</CreateTestPage> + <ValidateXaml>true</ValidateXaml> + <EnableOutOfBrowser>false</EnableOutOfBrowser> + <OutOfBrowserSettingsFile>Properties\OutOfBrowserSettings.xml</OutOfBrowserSettingsFile> + <UsePlatformExtensions>false</UsePlatformExtensions> + <ThrowErrorsInValidation>true</ThrowErrorsInValidation> + <LinkedServerProject> + </LinkedServerProject> + <SignManifests>false</SignManifests> + <TargetFrameworkProfile /> + </PropertyGroup> + <!-- This property group is only here to support building this project using the + MSBuild 3.5 toolset. In order to work correctly with this older toolset, it needs + to set the TargetFrameworkVersion to v3.5 --> + <PropertyGroup Condition="'$(MSBuildToolsVersion)' == '3.5'"> + <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>..\..\build\</OutputPath> + <DefineConstants>DEBUG;TRACE;SILVERLIGHT</DefineConstants> + <NoStdLib>true</NoStdLib> + <NoConfig>true</NoConfig> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>..\..\src\</OutputPath> + <DefineConstants>TRACE;SILVERLIGHT</DefineConstants> + <NoStdLib>true</NoStdLib> + <NoConfig>true</NoConfig> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <DocumentationFile> + </DocumentationFile> + </PropertyGroup> + <ItemGroup> + <Reference Include="mscorlib" /> + <Reference Include="System.Windows" /> + <Reference Include="system" /> + <Reference Include="System.Core" /> + <Reference Include="System.Net" /> + <Reference Include="System.Xml" /> + <Reference Include="System.Windows.Browser" /> + </ItemGroup> + <ItemGroup> + <Compile Include="App.xaml.cs"> + <DependentUpon>App.xaml</DependentUpon> + </Compile> + <Compile Include="MainPage.xaml.cs"> + <DependentUpon>MainPage.xaml</DependentUpon> + </Compile> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ApplicationDefinition Include="App.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </ApplicationDefinition> + <Page Include="MainPage.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + </ItemGroup> + <ItemGroup> + <None Include="Properties\AppManifest.xml" /> + </ItemGroup> + <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Silverlight\$(SilverlightVersion)\Microsoft.Silverlight.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> + <ProjectExtensions> + <VisualStudio> + <FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}"> + <SilverlightProjectProperties /> + </FlavorProperties> + </VisualStudio> + </ProjectExtensions> +</Project> \ No newline at end of file diff --git a/js/mediaelement/src/silverlight/SilverlightMediaElement.csproj.user b/js/mediaelement/src/silverlight/SilverlightMediaElement.csproj.user new file mode 100644 index 0000000000000000000000000000000000000000..2fe79f9ea2875ec62e87c83161d45d1ab776f880 --- /dev/null +++ b/js/mediaelement/src/silverlight/SilverlightMediaElement.csproj.user @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ProjectView>ShowAllFiles</ProjectView> + </PropertyGroup> + <ProjectExtensions> + <VisualStudio> + <FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}"> + <SilverlightProjectProperties> + <StartPageUrl> + </StartPageUrl> + <StartAction>DynamicPage</StartAction> + <AspNetDebugging>True</AspNetDebugging> + <NativeDebugging>False</NativeDebugging> + <SQLDebugging>False</SQLDebugging> + <ExternalProgram> + </ExternalProgram> + <StartExternalURL> + </StartExternalURL> + <StartCmdLineArguments> + </StartCmdLineArguments> + <StartWorkingDirectory> + </StartWorkingDirectory> + <ShowWebRefOnDebugPrompt>True</ShowWebRefOnDebugPrompt> + <OutOfBrowserProjectToDebug> + </OutOfBrowserProjectToDebug> + <ShowRiaSvcsOnDebugPrompt>True</ShowRiaSvcsOnDebugPrompt> + </SilverlightProjectProperties> + </FlavorProperties> + </VisualStudio> + </ProjectExtensions> +</Project> \ No newline at end of file diff --git a/js/mediaelement/src/silverlight/SilverlightMediaElement.sln b/js/mediaelement/src/silverlight/SilverlightMediaElement.sln new file mode 100644 index 0000000000000000000000000000000000000000..b3110c8436c2fe6b13dc487996d7c3ee853647b0 --- /dev/null +++ b/js/mediaelement/src/silverlight/SilverlightMediaElement.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilverlightMediaElement", "SilverlightMediaElement.csproj", "{CE832DD6-B659-4F3E-B49B-F297E2AC923A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CE832DD6-B659-4F3E-B49B-F297E2AC923A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE832DD6-B659-4F3E-B49B-F297E2AC923A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE832DD6-B659-4F3E-B49B-F297E2AC923A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE832DD6-B659-4F3E-B49B-F297E2AC923A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/js/mediaelement/test/Spec-CreateRemove.js b/js/mediaelement/test/Spec-CreateRemove.js new file mode 100644 index 0000000000000000000000000000000000000000..f70a5fd76ebfae17779719b0c428b18caf8020e9 --- /dev/null +++ b/js/mediaelement/test/Spec-CreateRemove.js @@ -0,0 +1,46 @@ +describe("HTMLMediaElement", function() { + var player; + var element; + var parentNode; + var domElem; + var NETWORK_EMPTY = 0, NETWORK_IDLE = 1, NETWORK_LOADING = 2, NETWORK_NO_SOURCE = 3; + var HAVE_NOTHING = 0, HAVE_METADATA = 1, HAVE_CURRENT_DATA = 2, HAVE_FUTURE_DATA = 3, HAVE_ENOUGH_DATA = 4; + var METADATA_TIMEOUT = 500, ENOUGH_DATA_TIMEOUT = 1000; + + beforeEach(function() { + }); + + afterEach(function() { + }); + + it("should be able to create and remove a player and clean up everything", function() { + runs(function() { + $('body').prepend('<video width="640" height="360" id="player1" poster="../media/echo-hereweare.jpg">' + + '<source type="video/mp4" src="../media/echo-hereweare.mp4" ></source>' + + '<source type="video/webm" src="../media/echo-hereweare.webm" ></source>' + + '</video>'); + player = new MediaElementPlayer('#player1', { + enableAutosize: false, + success: function(mediaElement, domObject) { + element = mediaElement; + domElem = domObject; + parentNode = domObject.parentNode + } + }); + }); + waitsFor(function() { + return element !== null; + }, "MediaElement should have loaded", 5000); + runs(function() { + expect(element.pluginType).toEqual('native'); + player.remove(); + player = null; + element = null; + domElem = null; + // Issue #670: https://github.com/johndyer/mediaelement/issues/670 + expect(Object.keys(mejs.players).length).toEqual(0); + + }); + }); + +}); diff --git a/js/mediaelement/test/Spec-Player.js b/js/mediaelement/test/Spec-Player.js new file mode 100644 index 0000000000000000000000000000000000000000..db0353a540bf60954cc26d9546b51cdedbb240c3 --- /dev/null +++ b/js/mediaelement/test/Spec-Player.js @@ -0,0 +1,143 @@ +describe("HTMLMediaElement", function() { + var player; + var element; + var domElem; + var NETWORK_EMPTY = 0, NETWORK_IDLE = 1, NETWORK_LOADING = 2, NETWORK_NO_SOURCE = 3; + var HAVE_NOTHING = 0, HAVE_METADATA = 1, HAVE_CURRENT_DATA = 2, HAVE_FUTURE_DATA = 3, HAVE_ENOUGH_DATA = 4; + var METADATA_TIMEOUT = 500, ENOUGH_DATA_TIMEOUT = 1000; + + beforeEach(function() { + $('body').prepend('<video width="640" height="360" id="player1" poster="../media/echo-hereweare.jpg">' + + '<source type="video/mp4" src="../media/echo-hereweare.mp4" ></source>' + + '<source type="video/webm" src="../media/echo-hereweare.webm" ></source>' + + '</video>'); + player = new MediaElementPlayer('#player1', { + enableAutosize: false, + success: function(mediaElement, domObject) { + element = mediaElement; + domElem = domObject; + } + }); + waitsFor(function() { + return element !== null; + }, "MediaElement should have loaded", 5000); + }); + + afterEach(function() { + player.remove(); + player = null; + element = null; + domElem = null; + }); + + it("should be of pluginType native", function() { + expect(element.pluginType).toEqual('native'); + }); + + it("should not be in Full Screen Mode", function() { + expect(element.isFullScreen).toEqual(false); + }); + + it("should be able to set video size", function() { + var expectedWidth = 200, expectedHeight = 100; + element.setVideoSize(expectedWidth, expectedHeight); + expect(element.width).toEqual(expectedWidth); + expect(element.height).toEqual(expectedHeight); + }); + + it("should be able to set volume", function() { + var expectedVolume = 0.5; + element.setVolume(expectedVolume); + expect(element.volume).toEqual(expectedVolume); + }); + + it("should be able to mute and un-mute", function() { + element.setMuted(true); + expect(element.muted).toEqual(true); + element.setMuted(false); + expect(element.muted).toEqual(false); + }); + + it("should be able to read the currentTime", function() { + expect(element.currentTime).toEqual(0); + }); + + it("should be able to read the paused state", function() { + expect(element.paused).toEqual(true); + }); + + it("should be able to set the play/paused state", function() { + element.play(); + expect(element.paused).toEqual(false); + element.pause(); + expect(element.paused).toEqual(true); + }); + + it("should implmenent stop() for parity with MEJS plugin versions", function() { + element.play(); + expect(element.paused).toEqual(false); + element.stop(); + expect(element.paused).toEqual(true); + }); + + it("should HAVE_METADATA within a timeout period", function() { + waitsFor(function() { + return element.readyState >= HAVE_METADATA; + }, "Metadata should be loaded", METADATA_TIMEOUT); + }); +}); + +describe("sourcechooser with the flash player", function() { + var player; + var element; + var domElem; + + beforeEach(function() { + $('body').prepend('<video width="640" height="360" id="player1" poster="../media/echo-hereweare.jpg">' + + '<source type="video/mp4" src="../media/other.mp4" title="Other MP$"></source>' + + '<source type="video/mp4" src="../media/echo-hereweare.mp4" title="MP4"></source>' + + '</video>'); + + player = new MediaElementPlayer('#player1', { + enableAutosize: false, + mode: 'shim', + flashName: '../build/flashmediaelement.swf', + features: ["playpause", "progress", "sourcechooser"], + success: function (mediaElement, domObject) { + $element = $(mediaElement); + } + }); + waitsFor(function () { + return element !== null; + }, "MediaElement should have loaded", 5000); + }); + + afterEach(function() { + player.remove(); + player = null; + element = null; + domElem = null; + }); + + it("should be able to switch sources when using the flash shim", function() { + waitsFor(function() { + return $('.mejs-sourcechooser-selector input[value$="media/echo-hereweare.mp4"]').length != 0 + }, "Timed Out", 10000); + runs(function() { + var $echoVideoRadioButton = $('.mejs-sourcechooser-selector input[value$="media/echo-hereweare.mp4"]'); + var $otherVideoRadioButton = $('.mejs-sourcechooser-selector input[value$="media/other.mp4"]'); + + expect($echoVideoRadioButton.length).toEqual(1); + expect($otherVideoRadioButton.length).toEqual(1); + expect(player.media.src).toMatch(/.*media\/other.mp4/); + expect(player.media.paused).toEqual(true); + + $echoVideoRadioButton.trigger('click'); + + expect(player.media.src).toMatch(/.*media\/echo-hereweare.mp4/) + + }); + }); + + +}); diff --git a/js/mediaelement/test/SpecRunner-CreateRemove.html b/js/mediaelement/test/SpecRunner-CreateRemove.html new file mode 100644 index 0000000000000000000000000000000000000000..fb20fb63c7dd75c456e3dee4276640ecdae79c59 --- /dev/null +++ b/js/mediaelement/test/SpecRunner-CreateRemove.html @@ -0,0 +1,78 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> + <title>Jasmine Spec Runner</title> + + <link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png"> + <link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css"> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> + + <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> + <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script> + + <script src="../build/jquery.js"></script> + + <!-- include source files here... --> + <script src="../src/js/me-header.js"></script> + <script src="../src/js/me-namespace.js"></script> + <script src="../src/js/me-utility.js"></script> + <script src="../src/js/me-plugindetector.js"></script> + <script src="../src/js/me-featuredetection.js"></script> + <script src="../src/js/me-mediaelements.js"></script> + <script src="../src/js/me-shim.js"></script> + <script src="../src/js/me-i18n.js"></script> + <script src="../src/js/me-i18n-locale-de.js"></script> + + <script src="../src/js/mep-header.js"></script> + <script src="../src/js/mep-library.js"></script> + <script src="../src/js/mep-player.js"></script> + <script src="../src/js/mep-feature-playpause.js"></script> + <script src="../src/js/mep-feature-stop.js"></script> + <script src="../src/js/mep-feature-progress.js"></script> + <script src="../src/js/mep-feature-time.js"></script> + <script src="../src/js/mep-feature-volume.js"></script> + <script src="../src/js/mep-feature-fullscreen.js"></script> + <script src="../src/js/mep-feature-tracks.js"></script> + <script src="../src/js/mep-feature-contextmenu.js"></script> + <script src="../src/js/mep-feature-postroll.js"></script> + + <!-- include spec files here... --> +<!-- <script src="SpecHelper.js"></script> --> + <script src="Spec-CreateRemove.js"></script> + + <script type="text/javascript"> + (function() { + var jasmineEnv = jasmine.getEnv(); + jasmineEnv.updateInterval = 1000; + + var htmlReporter = new jasmine.HtmlReporter(); + + jasmineEnv.addReporter(htmlReporter); + + jasmineEnv.specFilter = function(spec) { + return htmlReporter.specFilter(spec); + }; + + var currentWindowOnload = window.onload; + + window.onload = function() { + if (currentWindowOnload) { + currentWindowOnload(); + } + execJasmine(); + }; + + function execJasmine() { + jasmineEnv.execute(); + } + + })(); + </script> + +</head> + +<body> + <!-- VIDEO TAG GETS PREPENDED ABOVE HERE --> +</body> +</html> diff --git a/js/mediaelement/test/SpecRunner-Player.html b/js/mediaelement/test/SpecRunner-Player.html new file mode 100644 index 0000000000000000000000000000000000000000..82a7944e81ce4ae122b7334b6b5de74fd316a439 --- /dev/null +++ b/js/mediaelement/test/SpecRunner-Player.html @@ -0,0 +1,79 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> + <title>Jasmine Spec Runner</title> + + <link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png"> + <link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css"> + <link rel="stylesheet" href="../build/mediaelementplayer.min.css" /> + + <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> + <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script> + + <script src="../build/jquery.js"></script> + + <!-- include source files here... --> + <script src="../src/js/me-header.js"></script> + <script src="../src/js/me-namespace.js"></script> + <script src="../src/js/me-utility.js"></script> + <script src="../src/js/me-plugindetector.js"></script> + <script src="../src/js/me-featuredetection.js"></script> + <script src="../src/js/me-mediaelements.js"></script> + <script src="../src/js/me-shim.js"></script> + <script src="../src/js/me-i18n.js"></script> + <script src="../src/js/me-i18n-locale-de.js"></script> + + <script src="../src/js/mep-header.js"></script> + <script src="../src/js/mep-library.js"></script> + <script src="../src/js/mep-player.js"></script> + <script src="../src/js/mep-feature-playpause.js"></script> + <script src="../src/js/mep-feature-stop.js"></script> + <script src="../src/js/mep-feature-progress.js"></script> + <script src="../src/js/mep-feature-time.js"></script> + <script src="../src/js/mep-feature-volume.js"></script> + <script src="../src/js/mep-feature-fullscreen.js"></script> + <script src="../src/js/mep-feature-tracks.js"></script> + <script src="../src/js/mep-feature-contextmenu.js"></script> + <script src="../src/js/mep-feature-postroll.js"></script> + <script src="../src/js/mep-feature-sourcechooser.js"></script> + + <!-- include spec files here... --> +<!-- <script src="SpecHelper.js"></script> --> + <script src="Spec-Player.js"></script> + + <script type="text/javascript"> + (function() { + var jasmineEnv = jasmine.getEnv(); + jasmineEnv.updateInterval = 1000; + + var htmlReporter = new jasmine.HtmlReporter(); + + jasmineEnv.addReporter(htmlReporter); + + jasmineEnv.specFilter = function(spec) { + return htmlReporter.specFilter(spec); + }; + + var currentWindowOnload = window.onload; + + window.onload = function() { + if (currentWindowOnload) { + currentWindowOnload(); + } + execJasmine(); + }; + + function execJasmine() { + jasmineEnv.execute(); + } + + })(); + </script> + +</head> + +<body> + <!-- VIDEO TAG GETS PREPENDED ABOVE HERE --> +</body> +</html> diff --git a/js/mediaelement/test/lib/jasmine-1.3.1/MIT.LICENSE b/js/mediaelement/test/lib/jasmine-1.3.1/MIT.LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..7c435baaec86c0ebe2eb56b0550c11820c181b05 --- /dev/null +++ b/js/mediaelement/test/lib/jasmine-1.3.1/MIT.LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008-2011 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/js/mediaelement/test/lib/jasmine-1.3.1/jasmine-html.js b/js/mediaelement/test/lib/jasmine-1.3.1/jasmine-html.js new file mode 100644 index 0000000000000000000000000000000000000000..543d56963eb4a36750fef86842213505a7da0657 --- /dev/null +++ b/js/mediaelement/test/lib/jasmine-1.3.1/jasmine-html.js @@ -0,0 +1,681 @@ +jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter = function(_doc) { + var self = this; + var doc = _doc || window.document; + + var reporterView; + + var dom = {}; + + // Jasmine Reporter Public Interface + self.logRunningSpecs = false; + + self.reportRunnerStarting = function(runner) { + var specs = runner.specs() || []; + + if (specs.length == 0) { + return; + } + + createReporterDom(runner.env.versionString()); + doc.body.appendChild(dom.reporter); + setExceptionHandling(); + + reporterView = new jasmine.HtmlReporter.ReporterView(dom); + reporterView.addSpecs(specs, self.specFilter); + }; + + self.reportRunnerResults = function(runner) { + reporterView && reporterView.complete(); + }; + + self.reportSuiteResults = function(suite) { + reporterView.suiteComplete(suite); + }; + + self.reportSpecStarting = function(spec) { + if (self.logRunningSpecs) { + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } + }; + + self.reportSpecResults = function(spec) { + reporterView.specComplete(spec); + }; + + self.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } + }; + + self.specFilter = function(spec) { + if (!focusedSpecName()) { + return true; + } + + return spec.getFullName().indexOf(focusedSpecName()) === 0; + }; + + return self; + + function focusedSpecName() { + var specName; + + (function memoizeFocusedSpec() { + if (specName) { + return; + } + + var paramMap = []; + var params = jasmine.HtmlReporter.parameters(doc); + + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + specName = paramMap.spec; + })(); + + return specName; + } + + function createReporterDom(version) { + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, + dom.banner = self.createDom('div', { className: 'banner' }, + self.createDom('span', { className: 'title' }, "Jasmine "), + self.createDom('span', { className: 'version' }, version)), + + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), + dom.alert = self.createDom('div', {className: 'alert'}, + self.createDom('span', { className: 'exceptions' }, + self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), + self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), + dom.results = self.createDom('div', {className: 'results'}, + dom.summary = self.createDom('div', { className: 'summary' }), + dom.details = self.createDom('div', { id: 'details' })) + ); + } + + function noTryCatch() { + return window.location.search.match(/catch=false/); + } + + function searchWithCatch() { + var params = jasmine.HtmlReporter.parameters(window.document); + var removed = false; + var i = 0; + + while (!removed && i < params.length) { + if (params[i].match(/catch=/)) { + params.splice(i, 1); + removed = true; + } + i++; + } + if (jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + + return params.join("&"); + } + + function setExceptionHandling() { + var chxCatch = document.getElementById('no_try_catch'); + + if (noTryCatch()) { + chxCatch.setAttribute('checked', true); + jasmine.CATCH_EXCEPTIONS = false; + } + chxCatch.onclick = function() { + window.location.search = searchWithCatch(); + }; + } +}; +jasmine.HtmlReporter.parameters = function(doc) { + var paramStr = doc.location.search.substring(1); + var params = []; + + if (paramStr.length > 0) { + params = paramStr.split('&'); + } + return params; +} +jasmine.HtmlReporter.sectionLink = function(sectionName) { + var link = '?'; + var params = []; + + if (sectionName) { + params.push('spec=' + encodeURIComponent(sectionName)); + } + if (!jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + if (params.length > 0) { + link += params.join("&"); + } + + return link; +}; +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); +jasmine.HtmlReporter.ReporterView = function(dom) { + this.startedAt = new Date(); + this.runningSpecCount = 0; + this.completeSpecCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.skippedCount = 0; + + this.createResultsMenu = function() { + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), + ' | ', + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); + + this.summaryMenuItem.onclick = function() { + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); + }; + + this.detailsMenuItem.onclick = function() { + showDetails(); + }; + }; + + this.addSpecs = function(specs, specFilter) { + this.totalSpecCount = specs.length; + + this.views = { + specs: {}, + suites: {} + }; + + for (var i = 0; i < specs.length; i++) { + var spec = specs[i]; + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); + if (specFilter(spec)) { + this.runningSpecCount++; + } + } + }; + + this.specComplete = function(spec) { + this.completeSpecCount++; + + if (isUndefined(this.views.specs[spec.id])) { + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); + } + + var specView = this.views.specs[spec.id]; + + switch (specView.status()) { + case 'passed': + this.passedCount++; + break; + + case 'failed': + this.failedCount++; + break; + + case 'skipped': + this.skippedCount++; + break; + } + + specView.refresh(); + this.refresh(); + }; + + this.suiteComplete = function(suite) { + var suiteView = this.views.suites[suite.id]; + if (isUndefined(suiteView)) { + return; + } + suiteView.refresh(); + }; + + this.refresh = function() { + + if (isUndefined(this.resultsMenu)) { + this.createResultsMenu(); + } + + // currently running UI + if (isUndefined(this.runningAlert)) { + this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); + dom.alert.appendChild(this.runningAlert); + } + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); + + // skipped specs UI + if (isUndefined(this.skippedAlert)) { + this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); + } + + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.skippedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.skippedAlert); + } + + // passing specs UI + if (isUndefined(this.passedAlert)) { + this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); + } + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); + + // failing specs UI + if (isUndefined(this.failedAlert)) { + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); + } + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); + + if (this.failedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.failedAlert); + dom.alert.appendChild(this.resultsMenu); + } + + // summary info + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; + }; + + this.complete = function() { + dom.alert.removeChild(this.runningAlert); + + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.failedCount === 0) { + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); + } else { + showDetails(); + } + + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); + }; + + return this; + + function showDetails() { + if (dom.reporter.className.search(/showDetails/) === -1) { + dom.reporter.className += " showDetails"; + } + } + + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + + function isDefined(obj) { + return !isUndefined(obj); + } + + function specPluralizedFor(count) { + var str = count + " spec"; + if (count > 1) { + str += "s" + } + return str; + } + +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); + + +jasmine.HtmlReporter.SpecView = function(spec, dom, views) { + this.spec = spec; + this.dom = dom; + this.views = views; + + this.symbol = this.createDom('li', { className: 'pending' }); + this.dom.symbolSummary.appendChild(this.symbol); + + this.summary = this.createDom('div', { className: 'specSummary' }, + this.createDom('a', { + className: 'description', + href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.description) + ); + + this.detail = this.createDom('div', { className: 'specDetail' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.getFullName()) + ); +}; + +jasmine.HtmlReporter.SpecView.prototype.status = function() { + return this.getSpecStatus(this.spec); +}; + +jasmine.HtmlReporter.SpecView.prototype.refresh = function() { + this.symbol.className = this.status(); + + switch (this.status()) { + case 'skipped': + break; + + case 'passed': + this.appendSummaryToSuiteDiv(); + break; + + case 'failed': + this.appendSummaryToSuiteDiv(); + this.appendFailureDetail(); + break; + } +}; + +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { + this.summary.className += ' ' + this.status(); + this.appendToSummary(this.spec, this.summary); +}; + +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { + this.detail.className += ' ' + this.status(); + + var resultItems = this.spec.results().getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + this.detail.appendChild(messagesDiv); + this.dom.details.appendChild(this.detail); + } +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { + this.suite = suite; + this.dom = dom; + this.views = views; + + this.element = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) + ); + + this.appendToSummary(this.suite, this.element); +}; + +jasmine.HtmlReporter.SuiteView.prototype.status = function() { + return this.getSpecStatus(this.suite); +}; + +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { + this.element.className += " " + this.status(); +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); + +/* @deprecated Use jasmine.HtmlReporter instead + */ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/js/mediaelement/test/lib/jasmine-1.3.1/jasmine.css b/js/mediaelement/test/lib/jasmine-1.3.1/jasmine.css new file mode 100644 index 0000000000000000000000000000000000000000..8c008dc7221b2395341e0a6701f40f02a83a0ca6 --- /dev/null +++ b/js/mediaelement/test/lib/jasmine-1.3.1/jasmine.css @@ -0,0 +1,82 @@ +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } + +#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } +#HTMLReporter a { text-decoration: none; } +#HTMLReporter a:hover { text-decoration: underline; } +#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } +#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } +#HTMLReporter #jasmine_content { position: fixed; right: 100%; } +#HTMLReporter .version { color: #aaaaaa; } +#HTMLReporter .banner { margin-top: 14px; } +#HTMLReporter .duration { color: #aaaaaa; float: right; } +#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } +#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } +#HTMLReporter .symbolSummary li.passed { font-size: 14px; } +#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } +#HTMLReporter .symbolSummary li.failed { line-height: 9px; } +#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } +#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } +#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } +#HTMLReporter .symbolSummary li.pending { line-height: 11px; } +#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } +#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } +#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +#HTMLReporter .runningAlert { background-color: #666666; } +#HTMLReporter .skippedAlert { background-color: #aaaaaa; } +#HTMLReporter .skippedAlert:first-child { background-color: #333333; } +#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } +#HTMLReporter .passingAlert { background-color: #a6b779; } +#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } +#HTMLReporter .failingAlert { background-color: #cf867e; } +#HTMLReporter .failingAlert:first-child { background-color: #b03911; } +#HTMLReporter .results { margin-top: 14px; } +#HTMLReporter #details { display: none; } +#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } +#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter.showDetails .summary { display: none; } +#HTMLReporter.showDetails #details { display: block; } +#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter .summary { margin-top: 14px; } +#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } +#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } +#HTMLReporter .summary .specSummary.failed a { color: #b03911; } +#HTMLReporter .description + .suite { margin-top: 0; } +#HTMLReporter .suite { margin-top: 14px; } +#HTMLReporter .suite a { color: #333333; } +#HTMLReporter #details .specDetail { margin-bottom: 28px; } +#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } +#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } +#HTMLReporter .resultMessage span.result { display: block; } +#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } + +#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } +#TrivialReporter a:visited, #TrivialReporter a { color: #303; } +#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } +#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } +#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } +#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } +#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } +#TrivialReporter .runner.running { background-color: yellow; } +#TrivialReporter .options { text-align: right; font-size: .8em; } +#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } +#TrivialReporter .suite .suite { margin: 5px; } +#TrivialReporter .suite.passed { background-color: #dfd; } +#TrivialReporter .suite.failed { background-color: #fdd; } +#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } +#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } +#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } +#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } +#TrivialReporter .spec.skipped { background-color: #bbb; } +#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } +#TrivialReporter .passed { background-color: #cfc; display: none; } +#TrivialReporter .failed { background-color: #fbb; } +#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } +#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } +#TrivialReporter .resultMessage .mismatch { color: black; } +#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } +#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } +#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } +#TrivialReporter #jasmine_content { position: fixed; right: 100%; } +#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/js/mediaelement/test/lib/jasmine-1.3.1/jasmine.js b/js/mediaelement/test/lib/jasmine-1.3.1/jasmine.js new file mode 100644 index 0000000000000000000000000000000000000000..6b3459b913ffe7d15f30f8a93d5f7b6d5939ab7e --- /dev/null +++ b/js/mediaelement/test/lib/jasmine-1.3.1/jasmine.js @@ -0,0 +1,2600 @@ +var isCommonJS = typeof window == "undefined" && typeof exports == "object"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Maximum levels of nesting that will be included when an object is pretty-printed + */ +jasmine.MAX_PRETTY_PRINT_DEPTH = 40; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +/** + * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite. + * Set to false to let the exception bubble up in the browser. + * + */ +jasmine.CATCH_EXCEPTIONS = true; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the + * attributes on the object. + * + * @example + * // don't care about any other attributes than foo. + * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); + * + * @param sample {Object} sample + * @returns matchable object for the sample + */ +jasmine.objectContaining = function (sample) { + return new jasmine.Matchers.ObjectContaining(sample); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to <code>jasmine.log</code> in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a <em>disabled</em> Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + * @return {jasmine.Matchers} + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.source != b.source) + mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/"); + + if (a.ignoreCase != b.ignoreCase) + mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.global != b.global) + mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.multiline != b.multiline) + mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.sticky != b.sticky) + mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier"); + + return (mismatchValues.length === 0); +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a.jasmineMatches) { + return a.jasmineMatches(b); + } + + if (b.jasmineMatches) { + return b.jasmineMatches(a); + } + + if (a instanceof jasmine.Matchers.ObjectContaining) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.ObjectContaining) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (a instanceof RegExp && b instanceof RegExp) { + return this.compareRegExps_(a, b, mismatchKeys, mismatchValues); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + if (!jasmine.CATCH_EXCEPTIONS) { + this.func.apply(this.spec); + } + else { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that compares the actual to NaN. + */ +jasmine.Matchers.prototype.toBeNaN = function() { + this.message = function() { + return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ]; + }; + + return (this.actual !== this.actual); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."; + var positiveMessage = ""; + if (this.actual.callCount === 0) { + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called."; + } else { + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '') + } + return [positiveMessage, invertedMessage]; + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision, as number of decimal places + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2); +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} [expected] + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineToString = function() { + return '<jasmine.any(' + this.expectedClass + ')>'; +}; + +jasmine.Matchers.ObjectContaining = function (sample) { + this.sample = sample; +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + var env = jasmine.getEnv(); + + var hasKey = function(obj, keyName) { + return obj != null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in this.sample) { + if (!hasKey(other, property) && hasKey(this.sample, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); + } + } + + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { + return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>"; +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if <b>everything</b> below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar('<global>'); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!obj.hasOwnProperty(property)) continue; + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Array"); + return; + } + + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Object"); + return; + } + + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append('<getter>'); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + + // parallel to blocks. each true value in this array means the block will + // get executed even if we abort + this.ensured = []; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.blocks.unshift(block); + this.ensured.unshift(ensure); +}; + +jasmine.Queue.prototype.add = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.blocks.push(block); + this.ensured.push(ensure); +}; + +jasmine.Queue.prototype.insertNext = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.ensured.splice((this.index + this.offset + 1), 0, ensure); + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to <code>jasmine.log</code> in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this), true); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 3, + "build": 1, + "revision": 1354556913 +}; diff --git a/js/mediaelement/test/test.html b/js/mediaelement/test/test.html new file mode 100644 index 0000000000000000000000000000000000000000..f8aa0c0446573c37ab34341b6b4d0fc1dbedb6e5 --- /dev/null +++ b/js/mediaelement/test/test.html @@ -0,0 +1,275 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <title>HTML5 MediaElement</title> + + <script src="../build/jquery.js"></script> + + <script src="../src/js/me-namespace.js" type="text/javascript"></script> + <script src="../src/js/me-utility.js" type="text/javascript"></script> + <script src="../src/js/me-i18n.js" type="text/javascript"></script> + <script src="../src/js/me-plugindetector.js" type="text/javascript"></script> + <script src="../src/js/me-featuredetection.js" type="text/javascript"></script> + <script src="../src/js/me-mediaelements.js" type="text/javascript"></script> + <script src="../src/js/me-shim.js" type="text/javascript"></script> + + <script src="../src/js/mep-library.js" type="text/javascript"></script> + <script src="../src/js/mep-player.js" type="text/javascript"></script> + <script src="../src/js/mep-feature-playpause.js" type="text/javascript"></script> + <script src="../src/js/mep-feature-progress.js" type="text/javascript"></script> + <script src="../src/js/mep-feature-time.js" type="text/javascript"></script> + <script src="../src/js/mep-feature-speed.js" type="text/javascript"></script> + <script src="../src/js/mep-feature-tracks.js" type="text/javascript"></script> + <script src="../src/js/mep-feature-volume.js" type="text/javascript"></script> + <script src="../src/js/mep-feature-stop.js" type="text/javascript"></script> + <script src="../src/js/mep-feature-fullscreen.js" type="text/javascript"></script> + <link rel="stylesheet" href="../src/css/mediaelementplayer.css" /> + <link rel="stylesheet" href="../src/css/mejs-skins.css" /> + + <style> + #container{ + width: 700px; + margin: 20px auto; + } + + #video-container { + -sdisplay: none; + + } + </style> +</head> +<body> + +<div id="container"> + +<h1>MediaElementPlayer.js</h1> + +<p>Recommended Setup</p> + +<form> + + +<h2>MP3 audio (as src)</h2> + +<audio id="player2" src="../media/AirReview-Landmarks-02-ChasingCorporate.mp3" preload="none" type="audio/mp3" controls="controls"> + <p>Your browser leaves much to be desired.</p> +</audio> + +<span id="audio-mode"></span> + +<h2>MP4/WebM</h2> + + + +<div id="video-container"> + + <video width="640" height="360" id="player1" controls="controls" preload="none" poster="../media/echo-hereweare.jpg"> + + <source src="../media/echo-hereweare.mp4" type="video/mp4" /> + + <track kind="subtitles" src="../media/mediaelement.srt" srclang="en" ></track> + + </video> + +</div> + +<input id="showit" type="button" value="show it" /><br> + +<script> +$('#showit').on('click', function() { + if ($('#video-container').is(':visible')) { + $('#video-container').hide(); + } else { + $('#video-container').show(); + } + + $('#player1')[0].player.setControlsSize(); +}); +</script> + + +<span id="video-mode"></span> +<div style="min-height: 400px"> +<div id="video-events"></div> +<div id="video-props"></div> +</div> + +<video width="640" height="360" id="player2" controls="controls" preload="none" poster="../media/big_buck_bunny.jpg"> + <!-- + <source src="../media/big_buck_bunny.mp4" type="video/mp4" /> + --> + + <source src="../media/big_buck_bunny.mp4" type="video/mp4" /> +</video> + + +<br> + +<video width="640" height="360" id="player3" controls="controls" preload="none" poster="../media/big_buck_bunny.jpg"> + <!-- + <source src="../media/big_buck_bunny.mp4" type="video/mp4" /> + --> + + <source src="../media/big_buck_bunny.mp4" type="video/mp4" /> +</video> + + +</form> + +</div> + + +<script> +function appendMediaEvents($node, media) { + var + mediaEventNames = 'loadstart progress suspend abort error emptied stalled play pause loadedmetadata loadeddata waiting playing canplay canplaythrough seeking seeked timeupdate ended ratechange durationchange volumechange'.split(' '); + mediaEventTable = $('<table class="media-events"><caption>Media Events</caption><tbody></tbody></table>').appendTo($node).find('tbody'), + tr = null, + th = null, + td = null, + eventName = null, + il = 0, + i=0; + + for (il = mediaEventNames.length;i<il;i++) { + eventName = mediaEventNames[i]; + th = $('<th>' + eventName + '</th>'); + td = $('<td id="e_' + media.id + '_' + eventName + '" class="not-fired">0</td>'); + + if (tr == null) + tr = $("<tr/>"); + + tr.append(th); + tr.append(td); + + if ((i+1) % 5 == 0) { + mediaEventTable.append(tr); + tr = null; + } + + // listen for event + media.addEventListener(eventName, function(e) { + + var notice = $('#e_' + media.id + '_' + e.type), + number = parseInt(notice.html(), 10); + + notice + .html(number+1) + .attr('class','fired'); + }, true); + } + + mediaEventTable.append(tr); +} + +function appendMediaProperties($node, media) { + var /* src currentSrc */ + mediaPropertyNames = 'error currentSrc networkState preload buffered bufferedBytes bufferedTime readyState seeking currentTime initialTime duration startOffsetTime paused defaultPlaybackRate playbackRate played seekable ended autoplay loop controls volume muted'.split(' '), + mediaPropertyTable = $('<table class="media-properties"><caption>Media Properties</caption><tbody></tbody></table>').appendTo($node).find('tbody'), + tr = null, + th = null, + td = null, + propName = null, + il = 0, + i=0; + + for (il = mediaPropertyNames.length; i<il; i++) { + propName = mediaPropertyNames[i]; + th = $('<th>' + propName + '</th>'); + td = $('<td id="p_' + media.id + '_' + propName + '" class=""></td>'); + + if (tr == null) + tr = $("<tr/>"); + + tr.append(th); + tr.append(td); + + if ((i+1) % 3 == 0) { + mediaPropertyTable.append(tr); + tr = null; + } + } + + setInterval(function() { + var + propName = '', + val = null, + td = null; + + for (i = 0, il = mediaPropertyNames.length; i<il; i++) { + propName = mediaPropertyNames[i]; + td = $('#p_' + media.id + '_' + propName); + val = media[propName]; + val = + (typeof val == 'undefined') ? + 'undefined' : (val == null) ? 'null' : val.toString(); + td.html(val); + } + }, 500); + +} + +</script> + + +<script type="text/javascript"> +$('audio, video').bind('error', function(e) { + + //console.log('error',this, e, this.src, this.error.code); +}); + +jQuery(document).ready(function() { + $('audio, video').mediaelementplayer({ + //mode: 'shim', + + pluginPath:'../build/', + enablePluginSmoothing:true, + //duration: 489, + //startVolume: 0.4, + enablePluginDebug: true, + //iPadUseNativeControls: true, + //mode: 'shim', + //forcePluginFullScreen: true, + //usePluginFullScreen: true, + //mode: 'native', + //plugins: ['silverlight'], + features: ['playpause','progress','volume','speed','fullscreen'], + success: function(me,node) { + // report type + var tagName = node.tagName.toLowerCase(); + $('#' + tagName + '-mode').html( me.pluginType + ': success' + ', touch: ' + mejs.MediaFeatures.hasTouch); + + + if (tagName == 'audio') { + + me.addEventListener('progress',function(e) { + //console.log(e); + }, false); + + } + + me.addEventListener('progress',function(e) { + //console.log(e); + }, false); + + + // add events + if (tagName == 'video' && node.id == 'player1') { + appendMediaProperties($('#' + tagName + '-props'), me); + appendMediaEvents($('#' + tagName + '-events'), me); + + } + } + }); + + + +}); + +</script> + + +</body> +</html> \ No newline at end of file diff --git a/js/recorder-conf.js b/js/recorder-conf.js new file mode 100644 index 0000000000000000000000000000000000000000..2c81b830ebd93a6f4602ae736ce593cf5cbf9fed --- /dev/null +++ b/js/recorder-conf.js @@ -0,0 +1,55 @@ +(function ($) { + Drupal.behaviors.jRecorder = { + attach: function(context, settings) { + Recorder.initialize({ + swfSrc: settings.recorder.swf, // URL to recorder.swf + // optional: + flashContainer: document.getElementById('item_rec' + settings.recorder.id), // (optional) element where recorder.swf will be placed (needs to be 230x140 px) + onFlashSecurity: function(){ // (optional) callback when the flash swf needs to be visible + // this allows you to hide/show the flashContainer element on demand. + }, + }); + $('#item_rec_but' + settings.recorder.id).click(function() { + Recorder.record({ + start: function(){ + //alert("recording starts now. press stop when youre done. and then play or upload if you want."); + }, + progress: function(milliseconds){ + document.getElementById("time_" + settings.recorder.id).innerHTML = timecode(milliseconds); + } + }); + }); + + $('#item_stop_but' + settings.recorder.id).click(function() { + Recorder.stop(); + }); + + $('#item_play_but' + settings.recorder.id).click(function() { + Recorder.stop(); + Recorder.play({ + progress: function(milliseconds){ + document.getElementById("time_" + settings.recorder.id).innerHTML = timecode(milliseconds); + } + }); + }); + + function timecode(ms) { + var hms = { + h: Math.floor(ms/(60*60*1000)), + m: Math.floor((ms/60000) % 60), + s: Math.floor((ms/1000) % 60) + }; + var tc = []; // Timecode array to be joined with '.' + + if (hms.h > 0) { + tc.push(hms.h); + } + + tc.push((hms.m < 10 && hms.h > 0 ? "0" + hms.m : hms.m)); + tc.push((hms.s < 10 ? "0" + hms.s : hms.s)); + + return tc.join(':'); + } + } + } +})(jQuery); diff --git a/js/recorder/recorder.js b/js/recorder/recorder.js new file mode 100644 index 0000000000000000000000000000000000000000..847fa89fc086014afb259cd13f8598c108a519ec --- /dev/null +++ b/js/recorder/recorder.js @@ -0,0 +1,215 @@ +var Recorder = { + version: 1.13, + swfObject: null, + _callbacks: {}, + _events: {}, + _initialized: false, + _flashBlockCatched: false, + options: {}, + initialize: function(options){ + this.options = options || {}; + + if(window.location.protocol === "file:"){ + throw new Error("Due to Adobe Flash restrictions it is not possible to use the Recorder through the file:// protocol. Please use an http server."); + } + + if(!this.options.flashContainer){ + this._setupFlashContainer(); + } + + this.bind('initialized', function(){ + Recorder._initialized = true; + if(Recorder._flashBlockCatched){ + Recorder._defaultOnHideFlash(); + } + if(options.initialized){ + options.initialized(); + } + }); + + this.bind('showFlash', this.options.onFlashSecurity || this._defaultOnShowFlash); + this._loadFlash(); + }, + + clear: function(){ + Recorder._events = {}; + }, + + record: function(options){ + options = options || {}; + this.clearBindings("recordingStart"); + this.clearBindings("recordingProgress"); + this.clearBindings("recordingCancel"); + + this.bind('recordingStart', this._defaultOnHideFlash); + this.bind('recordingCancel', this._defaultOnHideFlash); + // reload flash to allow mic permission dialog to show again + this.bind('recordingCancel', this._loadFlash); + + this.bind('recordingStart', options['start']); + this.bind('recordingProgress', options['progress']); + this.bind('recordingCancel', options['cancel']); + + this.flashInterface().record(); + }, + + stop: function(){ + return this.flashInterface()._stop(); + }, + + play: function(options){ + options = options || {}; + this.clearBindings("playingProgress"); + this.bind('playingProgress', options['progress']); + this.bind('playingStop', options['finished']); + + this.flashInterface()._play(); + }, + + upload: function(options){ + options.audioParam = options.audioParam || "audio"; + options.params = options.params || {}; + this.clearBindings("uploadSuccess"); + this.bind("uploadSuccess", function(responseText){ + options.success(Recorder._externalInterfaceDecode(responseText)); + }); + + this.flashInterface().upload(options.url, options.audioParam, options.params); + }, + + audioData: function(newData){ + var delimiter = ";", newDataSerialized, stringData, data = [], sample; + if(newData){ + newDataSerialized = newData.join(";"); + } + stringData = this.flashInterface().audioData(newDataSerialized).split(delimiter); + for(var i=0; i < stringData.length; i ++){ + sample = parseFloat(stringData[i]); + if(!isNaN(sample)){ + data.push(sample); + } + } + return data; + }, + + request: function(method, uri, contentType, data, callback){ + var callbackName = this.registerCallback(callback); + this.flashInterface().request(method, uri, contentType, data, callbackName); + }, + + clearBindings: function(eventName){ + Recorder._events[eventName] = []; + }, + + bind: function(eventName, fn){ + if(!Recorder._events[eventName]){ Recorder._events[eventName] = [] } + Recorder._events[eventName].push(fn); + }, + + triggerEvent: function(eventName, arg0, arg1){ + Recorder._executeInWindowContext(function(){ + if (!Recorder._events[eventName]) { + return; + } + for(var i = 0, len = Recorder._events[eventName].length; i < len; i++){ + if(Recorder._events[eventName][i]){ + Recorder._events[eventName][i].apply(Recorder, [arg0, arg1]); + } + } + }); + }, + + triggerCallback: function(name, args){ + Recorder._executeInWindowContext(function(){ + Recorder._callbacks[name].apply(null, args); + }); + }, + + registerCallback: function(fn){ + var name = "CB" + parseInt(Math.random() * 999999, 10); + Recorder._callbacks[name] = fn; + return name; + }, + + flashInterface: function(){ + if(!this.swfObject){ + return null; + }else if(this.swfObject.record){ + return this.swfObject; + }else if(this.swfObject.children[3].record){ + return this.swfObject.children[3]; + } + }, + + _executeInWindowContext: function(fn){ + window.setTimeout(fn, 1); + }, + + _setupFlashContainer: function(){ + this.options.flashContainer = document.createElement("div"); + this.options.flashContainer.setAttribute("id", "recorderFlashContainer"); + this.options.flashContainer.setAttribute("style", "position: fixed; left: -9999px; top: -9999px; width: 230px; height: 140px; margin-left: 10px; border-top: 6px solid rgba(128, 128, 128, 0.6); border-bottom: 6px solid rgba(128, 128, 128, 0.6); border-radius: 5px 5px; padding-bottom: 1px; padding-right: 1px;"); + document.body.appendChild(this.options.flashContainer); + }, + + _clearFlash: function(){ + var flashElement = this.options.flashContainer.children[0]; + if(flashElement){ + this.options.flashContainer.removeChild(flashElement); + } + }, + + _loadFlash: function(){ + this._clearFlash(); + var flashElement = document.createElement("div"); + flashElement.setAttribute("id", "recorderFlashObject"); + this.options.flashContainer.appendChild(flashElement); + swfobject.embedSWF(this.options.swfSrc, "recorderFlashObject", "231", "141", "10.1.0", undefined, undefined, {allowscriptaccess: "always"}, undefined, function(e){ + if(e.success){ + Recorder.swfObject = e.ref; + Recorder._checkForFlashBlock(); + }else{ + Recorder._showFlashRequiredDialog(); + } + }); + }, + + _defaultOnShowFlash: function(){ + var flashContainer = Recorder.options.flashContainer; + flashContainer.style.left = ((window.innerWidth || document.body.offsetWidth) / 2) - 115 + "px"; + flashContainer.style.top = ((window.innerHeight || document.body.offsetHeight) / 2) - 70 + "px"; + }, + + _defaultOnHideFlash: function(){ + var flashContainer = Recorder.options.flashContainer; + flashContainer.style.left = "-9999px"; + flashContainer.style.top = "-9999px"; + }, + + _checkForFlashBlock: function(){ + window.setTimeout(function(){ + if(!Recorder._initialized){ + Recorder._flashBlockCatched = true; + Recorder.triggerEvent("showFlash"); + } + }, 500); + }, + + _showFlashRequiredDialog: function(){ + Recorder.options.flashContainer.innerHTML = "<p>Adobe Flash Player 10.1 or newer is required to use this feature.</p><p><a href='http://get.adobe.com/flashplayer' target='_top'>Get it on Adobe.com.</a></p>"; + Recorder.options.flashContainer.style.color = "white"; + Recorder.options.flashContainer.style.backgroundColor = "#777"; + Recorder.options.flashContainer.style.textAlign = "center"; + Recorder.triggerEvent("showFlash"); + }, + + _externalInterfaceDecode: function(data){ + return data.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%"); + } +}; + + +if(swfobject==undefined){ + /* SWFObject v2.2 <http://code.google.com/p/swfobject/> is released under the MIT License <http://www.opensource.org/licenses/mit-license.php */ + var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+encodeURI(O.location).toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}(); +} diff --git a/js/recorder/recorder.swf b/js/recorder/recorder.swf new file mode 100644 index 0000000000000000000000000000000000000000..630f201544af7e5c433061a6728ceb6794b4e334 Binary files /dev/null and b/js/recorder/recorder.swf differ diff --git a/js/slide.js b/js/slide.js new file mode 100755 index 0000000000000000000000000000000000000000..9279dbe60a6565b59587a490c31635f191785a31 --- /dev/null +++ b/js/slide.js @@ -0,0 +1,8 @@ +Drupal.behaviors.slide = function(context) { + $("div.divContent").hide(); + $("h3.expandable").click(function(){ + $(this).next().slideToggle(); // if your divs are after the plus, use next() instead + return false; + }); + +} \ No newline at end of file diff --git a/js/textboxColorChanger.js b/js/textboxColorChanger.js new file mode 100644 index 0000000000000000000000000000000000000000..c85ab34a2e270078857e47659443ff799c1c83cc --- /dev/null +++ b/js/textboxColorChanger.js @@ -0,0 +1,18 @@ +(function($) { + Drupal.behaviors.qtici = { + attach: function (context, settings) { + var counter = 0; + $(settings.qtici.qtici_textbox, context).each(function() { + $('#edit-textbox-' + this).css('outline-color', settings.qtici.qtici_color[counter]); + $('#edit-textbox-' + this).css('outline-width', 'medium'); + $('#edit-textbox-' + this).css('outline-style', 'solid'); + //$('#edit-textbox-' + this).css('-moz-border-bottom-colors', settings.qtici.qtici_color[counter]); + //$('#edit-textbox-' + this).css('border-bottom-width', 'medium'); + //$('#edit-textbox-' + this).css('border-top-width', 'thin'); + //$('#edit-textbox-' + this).css('border-right-width', 'thin'); + //$('#edit-textbox-' + this).css('border-left-width', 'thin'); + counter = counter + 1; + }); + }, + }; +})(jQuery); diff --git a/js/timeSpent.js b/js/timeSpent.js new file mode 100755 index 0000000000000000000000000000000000000000..f2a866289e7121e63393ccb6f70da25258a881ae --- /dev/null +++ b/js/timeSpent.js @@ -0,0 +1,19 @@ +(function ($) { + var start; + $(document).ready(function() { + var d = new Date(); + start = d.getTime(); + + $("form#qtici-pagepreview").submit(function() { + var d = new Date(); + end = d.getTime(); + timeSpent = end - start; + $("input[name=time]").val(timeSpent); + return true; + }); + + $("input[type=\"text\"]").keyup(function(){ + $(this).attr({width: "auto", size: $(this).val().length}); + }); + }); +})(jQuery); diff --git a/js/timer.js b/js/timer.js new file mode 100644 index 0000000000000000000000000000000000000000..aad8396d84eae91c62f5a73fd9dbe4c37814fa90 --- /dev/null +++ b/js/timer.js @@ -0,0 +1,51 @@ +(function($) { + Drupal.behaviors.YOURMODULE = { + attach: function(context, settings) { + + time = parseInt(Drupal.settings.qtici.time); + + var hoursleft = 0; + var minutesleft = time; // you can change these values to any value greater than 0 + var secondsleft = 0; + var millisecondsleft = 0; + var finishedtext = "Test beëindigd!" // text that appears when the countdown reaches 0 + end = new Date(); + end.setHours(end.getHours() + hoursleft); + end.setMinutes(end.getMinutes() + minutesleft); + end.setSeconds(end.getSeconds() + secondsleft); + end.setMilliseconds(end.getMilliseconds() + millisecondsleft); + function cd() { + now = new Date(); + diff = end - now; + diff = new Date(diff); + var msec = diff.getMilliseconds(); + var sec = diff.getSeconds(); + var min = diff.getMinutes(); + var hr = diff.getHours() - 1; + if (min < 10) { + min = "0" + min; + } + if (sec < 10) { + sec = "0" + sec; + } + if (msec < 10) { + msec = "00" + msec; + } + else if (msec < 100) { + msec = "0" + msec; + } + if (now >= end) { + clearTimeout(timerID); + document.getElementById("cdtime").innerHTML = finishedtext; + } + else { + document.getElementById("cdtime").innerHTML = hr + ":" + min + ":" + sec; + } // you can leave out the + ":" + msec if you want... + // If you do so, you should also change setTimeout to setTimeout("cd()",1000) + timerID = setTimeout(cd, 1000); + } + window.onload = cd; + + } + }; +})(jQuery); \ No newline at end of file diff --git a/js/videoQuestion.js b/js/videoQuestion.js new file mode 100644 index 0000000000000000000000000000000000000000..46a78215073f59bfa284c681d5a326f8a6830f67 --- /dev/null +++ b/js/videoQuestion.js @@ -0,0 +1,108 @@ +(function ($) { + + Drupal.qtici = Drupal.qtici || {}; + + Drupal.behaviors.qticiVID = { + attach: function(context, settings) { + $('[name="grip_button"]').each(function() { + $(this).click(function() { + var id = $(this).attr('id'); + var itemid = id.replace('get_time_player_', ''); + Drupal.qtici.f_time_player(itemid); + }); + }); + + $('[name="qclear_button"]').each(function() { + $(this).click(function() { + var id = $(this).attr('class'); + var itemid = id.replace('qtici_clear_button_', ''); + Drupal.qtici.clearAnswer(itemid); + }); + }); + } + }; + + Drupal.qtici.clearAnswer = function (itemid) { + //get the number of possibilities for the question, then you also know how much textboxes there are for the possibiity + number = document.getElementById("possibilitiesCount_" + itemid).value; + //check if there is only 1 possibiity + if (number === "0") { + //clear the only input field + document.getElementById("inputbox_time_player_" + itemid + "_id_0").value = ""; + } else { + //loop through all input fields + for (i = 0; i <= number; i++) { + //clear the current input field + document.getElementById("inputbox_time_player_" + itemid + "_id_" + i).value = ""; + } + } + //clear the hidden field that holds the times for checking them later + document.getElementById("VID_hidden_" + itemid).value = ""; + //get the name of the flowplayer you want to work with + var player = "player_" + itemid; + //reset the player to the beginning + $f(player).stop(); + }; + //template for displaying time in a nice format + String.prototype.toHHMMSS = function() { + sec_numb = parseInt(this, 10); // don't forget the second parm + var hours = Math.floor(sec_numb / 3600); + var minutes = Math.floor((sec_numb - (hours * 3600)) / 60); + var seconds = sec_numb - (hours * 3600) - (minutes * 60); + if (hours < 10) { + hours = "0" + hours; + } + if (minutes < 10) { + minutes = "0" + minutes; + } + + if (seconds < 10) { + seconds = "0" + seconds; + } + var time = minutes + ':' + seconds; + return time; + }; + + Drupal.qtici.f_time_player = function (id) { + //get the name of the flowplayer you want to work with + var player = "player_" + id; + //ask the state of the flowplayer playing, pauze,... + var status = $f(player).getState(); + //if the flowplayer is playing get the time + if (status == 3) { + //get the number of possibilities for the question, then you also know how much textboxes there are for the possibiity + number = document.getElementById("possibilitiesCount_" + id).value; + + //check if there is only 1 possibiity + if (number === "0") { + //when there is only one possibility check if it already has a time if not give it the current time + if (document.getElementById("inputbox_time_player_" + id + "_id_0").value === "") { + //set the current time in the textbox, the time is get in seconds and is rounded down to a full number, those seconds are converted with toHHMMSS to a nice time format + document.getElementById("inputbox_time_player_" + id + "_id_0").value = Math.floor($f(player).getTime()).toString().toHHMMSS(); + //add the current time to de hidden field for checking it later + document.getElementById("VID_hidden_" + id).value += $f(player).getTime() + "-"; + } + } else { + //if there are more than one input field go through them all + for (i = 0; i <= number; i++) { + //check if the current input field has an value + if (document.getElementById("inputbox_time_player_" + id + "_id_" + i).value === "") { + //set the current time in the textbox, the time is get in seconds and is rounded down to a full number, those seconds are converted with toHHMMSS to a nice time format + document.getElementById("inputbox_time_player_" + id + "_id_" + i).value = Math.floor($f(player).getTime()).toString().toHHMMSS(); + //add the current time to de hidden field for checking it later + document.getElementById("VID_hidden_" + id).value += $f(player).getTime() + "-"; + //if you gave this input field a time stop the for loop + i = number + 1; + } + } + } + //pauze the player + $f(player).pause(); + + } else { + //when the flowplayer isn't playing start him + $f(player).play(); + } + return false; + }; +})(jQuery); diff --git a/mobile/LICENSE.txt b/mobile/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/mobile/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/mobile/README.txt b/mobile/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..f78fe1c6ac81e3d43ae81eaaf39e20a38c80ce25 --- /dev/null +++ b/mobile/README.txt @@ -0,0 +1,52 @@ +Mobile theme HTML5 version for Drupal 7 +--------------------------------------- + +## DESCRIPTION + +Mobile theme is designed to present a clean, readable version of the website for consumption on mobile devices. It can be used by itself or as a base theme for a custom mobile theme you develop. + +### Mobile Light + +This is a minimalist, clean theme for mobile devices only. It has a light design. + +### Mobile Dark + +This is a minimalist, clean theme for mobile devices only. It has a dark design. + +### Mobile + +This is the base theme for Mobile Light and Mobile Dark -- or your own custom theme -- and is really stripped down. This is intended for use as a base theme only. + +## INSTALLATION + +Simply copy the Mobile theme folder and contents into your /sites/all/themes folder (or in the appropriate sites folder for a mobile subdomain URI, as appropriate) and enable the theme you want. If you are creating your own custom child theme, you don't need to enable Mobile at all, just include it in your code base. + +## TIPS + +### User scalable + +Mobile is coded to allow users to zoom in. If you want to disable that in your theme, copy Mobile's html.tpl.php into your own theme, and edit the meta name viewport line to read: + + <meta name="viewport" content="width=device-width, target-densityDpi=160dpi, initial-scale=1, user-scalable=no"> + +Consider using Modernizr to keep your css clean while supporting various broswers. http://drupal.org/project/modernizr + +## CREDITS + +### Mobile Version 7.x-3.x + +Developer and Maintainer: + Laura Scott + http://drupal.org/user/18973 + Sponsored by http://pingv.com + +### Mobile Version 6.x, 7.x-1.x + +Maintainer: + Richard Eriksson + http://justagwailo.com/ + +### Original Author of http://drupal.org/project/mobile + +B�r Kessels +Sponsored by http://www.webschuur.com/ diff --git a/mobile/README2.txt b/mobile/README2.txt new file mode 100644 index 0000000000000000000000000000000000000000..ad2bdfddca64758930ff3dadcc41be2bebb07743 --- /dev/null +++ b/mobile/README2.txt @@ -0,0 +1,2 @@ +Copy this folder into your drupal theme folder and install this module: http://drupal.org/project/mobile_theme +This will allow you to use the qtici mobile theme with drupal. \ No newline at end of file diff --git a/mobile/logo.png b/mobile/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fe6aac8703098a1de7b9330068a274c70de94eaa Binary files /dev/null and b/mobile/logo.png differ diff --git a/mobile/mobile.info b/mobile/mobile.info new file mode 100644 index 0000000000000000000000000000000000000000..ce526584d111f6b5e8e857823910b33d7bc27de6 --- /dev/null +++ b/mobile/mobile.info @@ -0,0 +1,30 @@ +name = "Mobile Base HTML5" +description = "Minimalist base theme for mobile devices." +core = 7.x +version = 3.x +engine = phptemplate + +; Page layout regions +regions[page_top] = Page top +regions[highlighted] = Highlighted +regions[help] = Help +regions[content] = Content +regions[supplementary] = Supplementary content +regions[navigation] = Navigation +regions[footer] = Footer +regions[page_bottom] = Page bottom + +; Stylesheets +stylesheets[all][] = styles/normalize.css +stylesheets[screen][] = styles/mobile.css +stylesheets[print][] = styles/print.css + +; Default values for theme settings +settings[mobilewebapp] = 1 + +; Information added by drupal.org packaging script on 2013-02-04 +version = "7.x-3.x-dev" +core = "7.x" +project = "mobile" +datestamp = "1359940536" + diff --git a/mobile/mobile_light/logo.png b/mobile/mobile_light/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..93a7e1a3dfcdcca9281270de75d4f5616913e742 Binary files /dev/null and b/mobile/mobile_light/logo.png differ diff --git a/mobile/mobile_light/logooriginal.png b/mobile/mobile_light/logooriginal.png new file mode 100644 index 0000000000000000000000000000000000000000..fe6aac8703098a1de7b9330068a274c70de94eaa Binary files /dev/null and b/mobile/mobile_light/logooriginal.png differ diff --git a/mobile/mobile_light/logoplus.png b/mobile/mobile_light/logoplus.png new file mode 100644 index 0000000000000000000000000000000000000000..898dba05ba02bc479ba9f41796eafab44971d82c Binary files /dev/null and b/mobile/mobile_light/logoplus.png differ diff --git a/mobile/mobile_light/mobile_light.info b/mobile/mobile_light/mobile_light.info new file mode 100644 index 0000000000000000000000000000000000000000..9508cbf1a43e2c2c5ce1f17215c3b83418141580 --- /dev/null +++ b/mobile/mobile_light/mobile_light.info @@ -0,0 +1,19 @@ +name = "QTICI Mobile" +description = "A minimalist theme for Mobile" +core = 7.x +engine = phptemplate +base theme = mobile + +; Page layout regions +regions[page_top] = Page top +regions[highlighted] = Highlighted +regions[help] = Help +regions[content] = Content +regions[supplementary] = Supplementary content +regions[navigation] = Navigation +regions[footer] = Footer +regions[page_bottom] = Page bottom + +; Stylesheets +stylesheets[screen][] = styles/mobile-light.css + diff --git a/mobile/mobile_light/styles/mobile-light.css b/mobile/mobile_light/styles/mobile-light.css new file mode 100644 index 0000000000000000000000000000000000000000..8d972c9337a46105a86ecf110e72088b6357fe7b --- /dev/null +++ b/mobile/mobile_light/styles/mobile-light.css @@ -0,0 +1,109 @@ +body { + font-size: 83%; + font-family: Verdana, Tahoma, Trebuchet, sans-serif; + line-height:1.5em; + padding: 10px; + background: #467491; +} +a { + text-decoration: none; +} +img { + max-width: 100%; + height: auto; +} +h1 { + font-size: 1.6em; +} +h2 { + font-size: 1.4em; +} +h3 { + font-size: 1.3em; +} +h4 { + font-size: 1.2em; +} +h5 { + font-size: 1.1em; +} +body > header { + line-height: 1.1em; +} +a#logo img { + /*float: left;*/ + margin-right: 1em; +} +h1#site-name { + margin: 0; + padding-top: 1em; +} +h2#site-slogan { + margin-top: .5em; + font-size: 1em; +} +section[role=main] { + margin-top: 2em; +} +section[role=main] article.node { + background: #fff; + border: 1px solid #999; + border-radius: .5em; + margin-bottom: 2em; + padding: 1em; +} +article > header > h1 { + margin-top: 0; +} +section#comments { + background: #ddd; + border: 1px solid #999; + border-radius: .5em; + margin-bottom: 2em; + padding: 1em; +} +section#comments article.comment { + background: #eee; + border: 1px solid #999; + border-radius: .5em; + margin-bottom: 2em; + padding: 1em; +} +#supplementary .block { + background: #ddd; + border: 1px solid #999; + border-radius: .5em; + margin-bottom: 2em; + padding: 1em; +} +nav ul.menu { + padding: 0; +} +nav ul.menu li { + list-style: none; + margin: 0; +} +nav ul.menu li a, +nav ul#main-menu li a, +nav ul#secondary-menu li a { + background: #555; + border: 1px solid #999; + border-radius: .5em; + color: #fff; + margin-bottom: 2em; + padding: 1em; + text-align: center; + -webkit-transition: all 200ms ease-out; + -moz-transition: all 200ms ease-out; + -ms-transition: all 200ms ease-out; + -o-transition: all 200ms ease-out; + transition: all 200ms ease-out; +} +nav li a.active { + background: #eee; + border-color: #000; +} + +#block_exercises li a { + color: #000; +} \ No newline at end of file diff --git a/mobile/styles/mobile.css b/mobile/styles/mobile.css new file mode 100644 index 0000000000000000000000000000000000000000..74fb497db3227178c7626ef1bcab787f0d5f29bf --- /dev/null +++ b/mobile/styles/mobile.css @@ -0,0 +1,28 @@ +* { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +html { + height: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +article, +aside, +blockquote, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section { + display:block; +} +nav li a { + display: block; +} \ No newline at end of file diff --git a/mobile/styles/normalize.css b/mobile/styles/normalize.css new file mode 100644 index 0000000000000000000000000000000000000000..34d425b147a854638111575166a5cbf98f79d041 --- /dev/null +++ b/mobile/styles/normalize.css @@ -0,0 +1,375 @@ +/*! normalize.css c.f. https://github.com/necolas/normalize.css + +/* ========================================================================== + HTML5 display definitions + ========================================================================== */ + +/* + * Corrects `block` display not defined in IE 8/9. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section, +summary { + display: block; +} + +/* + * Corrects `inline-block` display not defined in IE 8/9. + */ + +audio, +canvas, +video { + display: inline-block; +} + +/* + * Prevents modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/* + * Addresses styling for `hidden` attribute not present in IE 8/9. + */ + +[hidden] { + display: none; +} + +/* ========================================================================== + Base + ========================================================================== */ + +/* + * 1. Sets default font family to sans-serif. + * 2. Prevents iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + -ms-text-size-adjust: 100%; /* 2 */ +} + +/* + * Removes default margin. + */ + +body { + margin: 0; +} + +/* ========================================================================== + Links + ========================================================================== */ + +/* + * Addresses `outline` inconsistency between Chrome and other browsers. + */ + +a:focus { + outline: thin dotted; +} + +/* + * Improves readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* ========================================================================== + Typography + ========================================================================== */ + +/* + * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, + * Safari 5, and Chrome. + */ + +h1 { + font-size: 2em; +} + +/* + * Addresses styling not present in IE 8/9, Safari 5, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/* + * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/* + * Addresses styling not present in Safari 5 and Chrome. + */ + +dfn { + font-style: italic; +} + +/* + * Addresses styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + + +/* + * Corrects font family set oddly in Safari 5 and Chrome. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + font-size: 1em; +} + +/* + * Improves readability of pre-formatted text in all browsers. + */ + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +/* + * Sets consistent quote types. + */ + +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} + +/* + * Addresses inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/* + * Prevents `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ========================================================================== + Embedded content + ========================================================================== */ + +/* + * Removes border when inside `a` element in IE 8/9. + */ + +img { + border: 0; +} + +/* + * Corrects overflow displayed oddly in IE 9. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* ========================================================================== + Figures + ========================================================================== */ + +/* + * Addresses margin not present in IE 8/9 and Safari 5. + */ + +figure { + margin: 0; +} + +/* ========================================================================== + Forms + ========================================================================== */ + +/* + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/* + * 1. Corrects color not being inherited in IE 8/9. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/* + * 1. Corrects font family not being inherited in all browsers. + * 2. Corrects font size not being inherited in all browsers. + * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome + */ + +button, +input, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 2 */ + margin: 0; /* 3 */ +} + +/* + * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +button, +input { + line-height: normal; +} + +/* + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Corrects inability to style clickable `input` types in iOS. + * 3. Improves usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/* + * Re-set default cursor for disabled elements. + */ + +button[disabled], +input[disabled] { + cursor: default; +} + +/* + * 1. Addresses box sizing set to `content-box` in IE 8/9. + * 2. Removes excess padding in IE 8/9. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/* + * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/* + * Removes inner padding and search cancel button in Safari 5 and Chrome + * on OS X. + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* + * Removes inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* + * 1. Removes default vertical scrollbar in IE 8/9. + * 2. Improves readability and alignment in all browsers. + */ + +textarea { + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ +} + +/* ========================================================================== + Tables + ========================================================================== */ + +/* + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/mobile/template.php b/mobile/template.php new file mode 100644 index 0000000000000000000000000000000000000000..e4975d3c772e47d5b01df9c2570b681f40103278 --- /dev/null +++ b/mobile/template.php @@ -0,0 +1,91 @@ +<?php + + +function mobile_preprocess_html(&$vars) { + // If "mobile web app" selected: + if (theme_get_setting('mobilewebapp')) { + // set showgrid variable for html.tpl.php + $vars['meta_mobilewebapp'] = 1; + } + else { + $vars['meta_mobilewebapp'] = 0; + } + +} + + +function mobile_preprocess_block(&$variables, $hook) { + // Use a template with no wrapper for the page's main content. + // Classes describing the position of the block within the region. + $variables['classes_array'][] = $variables['block_zebra']; + + $variables['title_attributes_array']['class'][] = 'block-title'; + + // Add Aria Roles via attributes. + switch ($variables['block']->module) { + case 'system': + switch ($variables['block']->delta) { + case 'main': + // Note: the "main" role goes in the page.tpl, not here. + break; + case 'help': + case 'powered-by': + $variables['attributes_array']['role'] = 'complementary'; + break; + default: + // Any other "system" block is a menu block. + $variables['attributes_array']['role'] = 'navigation'; + break; + } + break; + case 'menu': + $variables['attributes_array']['role'] = 'navigation'; + break; + case 'menu_block': + $variables['attributes_array']['role'] = 'navigation'; + break; + case 'blog': + case 'book': + $variables['attributes_array']['role'] = 'navigation'; + break; + case 'comment': + case 'forum': + case 'shortcut': + case 'statistics': + $variables['attributes_array']['role'] = 'navigation'; + break; + case 'search': + $variables['attributes_array']['role'] = 'search'; + break; + case 'help': + $variables['attributes_array']['role'] = 'note'; + break; + case 'aggregator': + case 'locale': + case 'poll': + case 'profile': + $variables['attributes_array']['role'] = 'complementary'; + break; + case 'node': + switch ($variables['block']->delta) { + case 'syndicate': + $variables['attributes_array']['role'] = 'complementary'; + break; + case 'recent': + $variables['attributes_array']['role'] = 'navigation'; + break; + } + break; + case 'user': + switch ($variables['block']->delta) { + case 'login': + $variables['attributes_array']['role'] = 'form'; + break; + case 'new': + case 'online': + $variables['attributes_array']['role'] = 'complementary'; + break; + } + break; + } +} \ No newline at end of file diff --git a/mobile/templates/block--comment.tpl.php b/mobile/templates/block--comment.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..3ebae60cfb4293afa9b2ffe17b6ed570aa89258b --- /dev/null +++ b/mobile/templates/block--comment.tpl.php @@ -0,0 +1,60 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + * + * HTML5 assumption: views blocks are usually nav elements. + * Use more specific templates to override this. + * Example, block--views--foo.tpl.php + */ +?> +<nav id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <?php print $content ?> +</nav> diff --git a/mobile/templates/block--node--recent.tpl.php b/mobile/templates/block--node--recent.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..3ebae60cfb4293afa9b2ffe17b6ed570aa89258b --- /dev/null +++ b/mobile/templates/block--node--recent.tpl.php @@ -0,0 +1,60 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + * + * HTML5 assumption: views blocks are usually nav elements. + * Use more specific templates to override this. + * Example, block--views--foo.tpl.php + */ +?> +<nav id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <?php print $content ?> +</nav> diff --git a/mobile/templates/block--node--syndicate.tpl.php b/mobile/templates/block--node--syndicate.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..f95ba3b2eef4c1266d5677dce8280e08678375f8 --- /dev/null +++ b/mobile/templates/block--node--syndicate.tpl.php @@ -0,0 +1,56 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + */ +?> +<section id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <?php print $content ?> +</section> diff --git a/mobile/templates/block--search.tpl.php b/mobile/templates/block--search.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..3ebae60cfb4293afa9b2ffe17b6ed570aa89258b --- /dev/null +++ b/mobile/templates/block--search.tpl.php @@ -0,0 +1,60 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + * + * HTML5 assumption: views blocks are usually nav elements. + * Use more specific templates to override this. + * Example, block--views--foo.tpl.php + */ +?> +<nav id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <?php print $content ?> +</nav> diff --git a/mobile/templates/block--system--main-menu.tpl.php b/mobile/templates/block--system--main-menu.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..b4319430215431af02c7fe35b9c83b098d8379f8 --- /dev/null +++ b/mobile/templates/block--system--main-menu.tpl.php @@ -0,0 +1,58 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + * + * HTML5 assumption: menu blocks are nav elements + */ +?> +<nav id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <?php print $content ?> +</nav> diff --git a/mobile/templates/block--system--main.tpl.php b/mobile/templates/block--system--main.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..092c6ac409bb3d3525cc4f60fb2af86882f503fa --- /dev/null +++ b/mobile/templates/block--system--main.tpl.php @@ -0,0 +1,47 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + */ +?> +<?php print $content ?> diff --git a/mobile/templates/block--system--navigation.tpl.php b/mobile/templates/block--system--navigation.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..b4319430215431af02c7fe35b9c83b098d8379f8 --- /dev/null +++ b/mobile/templates/block--system--navigation.tpl.php @@ -0,0 +1,58 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + * + * HTML5 assumption: menu blocks are nav elements + */ +?> +<nav id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <?php print $content ?> +</nav> diff --git a/mobile/templates/block--system--powered-by.tpl.php b/mobile/templates/block--system--powered-by.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..12158ebf9c9113959fc9cb962f32d583a4b792e5 --- /dev/null +++ b/mobile/templates/block--system--powered-by.tpl.php @@ -0,0 +1,57 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + */ +?> +<section id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <?php print $content ?> + +</section> diff --git a/mobile/templates/block--system--user-menu.tpl.php b/mobile/templates/block--system--user-menu.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..b4319430215431af02c7fe35b9c83b098d8379f8 --- /dev/null +++ b/mobile/templates/block--system--user-menu.tpl.php @@ -0,0 +1,58 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + * + * HTML5 assumption: menu blocks are nav elements + */ +?> +<nav id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <?php print $content ?> +</nav> diff --git a/mobile/templates/block--views.tpl.php b/mobile/templates/block--views.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..3ebae60cfb4293afa9b2ffe17b6ed570aa89258b --- /dev/null +++ b/mobile/templates/block--views.tpl.php @@ -0,0 +1,60 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + * + * HTML5 assumption: views blocks are usually nav elements. + * Use more specific templates to override this. + * Example, block--views--foo.tpl.php + */ +?> +<nav id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <?php print $content ?> +</nav> diff --git a/mobile/templates/block.tpl.php b/mobile/templates/block.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..1c128c05c47ec1ad1ec5c4483c626f6c9c996a63 --- /dev/null +++ b/mobile/templates/block.tpl.php @@ -0,0 +1,65 @@ +<?php + +/** + * @file + * Default theme implementation to display a block. + * + * Available variables: + * - $block->subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + */ +?> +<div id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>> + +<?php + /* + * helpful debugging, temporary + * Template: block--<?php print $block->module; ?>--<?php print $block->delta; ?>.tpl.php + */ +?> + + <?php print render($title_prefix); ?> +<?php if ($block->subject): ?> + <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2> +<?php endif;?> + <?php print render($title_suffix); ?> + + <div class="content"<?php print $content_attributes; ?>> + <?php print $content ?> + </div> +</div> diff --git a/mobile/templates/book-export-html.tpl.php b/mobile/templates/book-export-html.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..2f51f52e23a38c8065a965f0e6c53b063563c020 --- /dev/null +++ b/mobile/templates/book-export-html.tpl.php @@ -0,0 +1,52 @@ +<?php + +/** + * @file + * Default theme implementation for printed version of book outline. + * + * Available variables: + * - $title: Top level node title. + * - $head: Header tags. + * - $language: Language code. e.g. "en" for english. + * - $language_rtl: TRUE or FALSE depending on right to left language scripts. + * - $base_url: URL to home page. + * - $contents: Nodes within the current outline rendered through + * book-node-export-html.tpl.php. + * + * @see template_preprocess_book_export_html() + * + * @ingroup themeable + */ +?> +<!DOCTYPE html> +<html lang="<?php print $language->language; ?>" xml:lang="<?php print $language->language; ?>" dir="<?php print $dir; ?>"> + <head> + <title><?php print $title; ?></title> + <?php print $head; ?> + <base href="<?php print $base_url; ?>" /> + <link type="text/css" rel="stylesheet" href="misc/print.css" /> + <?php if ($language_rtl): ?> + <link type="text/css" rel="stylesheet" href="misc/print-rtl.css" /> + <?php endif; ?> + </head> + <body> + <?php + /** + * The given node is /embedded to its absolute depth in a top level + * section/. For example, a child node with depth 2 in the hierarchy is + * contained in (otherwise empty) <div> elements corresponding to + * depth 0 and depth 1. This is intended to support WYSIWYG output - e.g., + * level 3 sections always look like level 3 sections, no matter their + * depth relative to the node selected to be exported as printer-friendly + * HTML. + */ + $div_close = ''; + ?> + <?php for ($i = 1; $i < $depth; $i++): ?> + <div class="section-<?php print $i; ?>"> + <?php $div_close .= '</div>'; ?> + <?php endfor; ?> + <?php print $contents; ?> + <?php print $div_close; ?> + </body> +</html> diff --git a/mobile/templates/book-navigation.tpl.php b/mobile/templates/book-navigation.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..fc38e39b5c16d03417e4fc33c7b06728b6a36236 --- /dev/null +++ b/mobile/templates/book-navigation.tpl.php @@ -0,0 +1,54 @@ +<?php + +/** + * @file + * Default theme implementation to navigate books. + * + * Presented under nodes that are a part of book outlines. + * + * Available variables: + * - $tree: The immediate children of the current node rendered as an unordered + * list. + * - $current_depth: Depth of the current node within the book outline. Provided + * for context. + * - $prev_url: URL to the previous node. + * - $prev_title: Title of the previous node. + * - $parent_url: URL to the parent node. + * - $parent_title: Title of the parent node. Not printed by default. Provided + * as an option. + * - $next_url: URL to the next node. + * - $next_title: Title of the next node. + * - $has_links: Flags TRUE whenever the previous, parent or next data has a + * value. + * - $book_id: The book ID of the current outline being viewed. Same as the node + * ID containing the entire outline. Provided for context. + * - $book_url: The book/node URL of the current outline being viewed. Provided + * as an option. Not used by default. + * - $book_title: The book/node title of the current outline being viewed. + * Provided as an option. Not used by default. + * + * @see template_preprocess_book_navigation() + * + * @ingroup themeable + */ +?> +<?php if ($tree || $has_links): ?> + <nav id="book-navigation-<?php print $book_id; ?>" class="book-navigation"> + <?php print $tree; ?> + + <?php if ($has_links): ?> + <div class="page-links clearfix"> + <?php if ($prev_url): ?> + <a href="<?php print $prev_url; ?>" class="page-previous" title="<?php print t('Go to previous page'); ?>"><?php print t('‹ ') . $prev_title; ?></a> + <?php endif; ?> + <?php if ($parent_url): ?> + <a href="<?php print $parent_url; ?>" class="page-up" title="<?php print t('Go to parent page'); ?>"><?php print t('up'); ?></a> + <?php endif; ?> + <?php if ($next_url): ?> + <a href="<?php print $next_url; ?>" class="page-next" title="<?php print t('Go to next page'); ?>"><?php print $next_title . t(' ›'); ?></a> + <?php endif; ?> + </div> + <?php endif; ?> + + </nav> +<?php endif; ?> diff --git a/mobile/templates/book-node-export-html.tpl.php b/mobile/templates/book-node-export-html.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..50c878f5b5b9976d8aadfa0bcb17b40122059a6b --- /dev/null +++ b/mobile/templates/book-node-export-html.tpl.php @@ -0,0 +1,25 @@ +<?php + +/** + * @file + * Default theme implementation for a single node in a printer-friendly outline. + * + * @see book-node-export-html.tpl.php + * Where it is collected and printed out. + * + * Available variables: + * - $depth: Depth of the current node inside the outline. + * - $title: Node title. + * - $content: Node content. + * - $children: All the child nodes recursively rendered through this file. + * + * @see template_preprocess_book_node_export_html() + * + * @ingroup themeable + */ +?> +<article id="node-<?php print $node->nid; ?>" class="section-<?php print $depth; ?>"> + <h1 class="book-heading"><?php print $title; ?></h1> + <?php print $content; ?> + <?php print $children; ?> +</article> diff --git a/mobile/templates/comment-wrapper.tpl.php b/mobile/templates/comment-wrapper.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..b9876b5606dc0366942488777055cbd316cb6e6c --- /dev/null +++ b/mobile/templates/comment-wrapper.tpl.php @@ -0,0 +1,53 @@ +<?php + +/** + * @file + * Default theme implementation to provide an HTML container for comments. + * + * Available variables: + * - $content: The array of content-related elements for the node. Use + * render($content) to print them all, or + * print a subset such as render($content['comment_form']). + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default value has the following: + * - comment-wrapper: The current template type, i.e., "theming hook". + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * The following variables are provided for contextual information. + * - $node: Node object the comments are attached to. + * The constants below the variables show the possible values and should be + * used for comparison. + * - $display_mode + * - COMMENT_MODE_FLAT + * - COMMENT_MODE_THREADED + * + * Other variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess_comment_wrapper() + * @see theme_comment_wrapper() + */ +?> +<section id="comments" class="<?php print $classes; ?>"<?php print $attributes; ?>> + <?php if ($content['comments'] && $node->type != 'forum'): ?> + <?php print render($title_prefix); ?> + <h2 class="title"><?php print t('Comments'); ?></h2> + <?php print render($title_suffix); ?> + <?php endif; ?> + + <?php print render($content['comments']); ?> + + <?php if ($content['comment_form']): ?> + <div class="comment-form"> + <h2 class="title comment-form"><?php print t('Add new comment'); ?></h2> + <?php print render($content['comment_form']); ?> + </div><!--/.comment-form--> + <?php endif; ?> +</section> diff --git a/mobile/templates/comment.tpl.php b/mobile/templates/comment.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..c11ee484f23ee014b8c1dbe29200e6bf683698e5 --- /dev/null +++ b/mobile/templates/comment.tpl.php @@ -0,0 +1,94 @@ +<?php + +/** + * @file + * Default theme implementation for comments. + * + * Available variables: + * - $author: Comment author. Can be link or plain text. + * - $content: An array of comment items. Use render($content) to print them all, or + * print a subset such as render($content['field_example']). Use + * hide($content['field_example']) to temporarily suppress the printing of a + * given element. + * - $created: Formatted date and time for when the comment was created. + * Preprocess functions can reformat it by calling format_date() with the + * desired parameters on the $comment->created variable. + * - $changed: Formatted date and time for when the comment was last changed. + * Preprocess functions can reformat it by calling format_date() with the + * desired parameters on the $comment->changed variable. + * - $new: New comment marker. + * - $permalink: Comment permalink. + * - $submitted: Submission information created from $author and $created during + * template_preprocess_comment(). + * - $picture: Authors picture. + * - $signature: Authors signature. + * - $status: Comment status. Possible values are: + * comment-unpublished, comment-published or comment-preview. + * - $title: Linked title. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the following: + * - comment: The current template type, i.e., "theming hook". + * - comment-by-anonymous: Comment by an unregistered user. + * - comment-by-node-author: Comment by the author of the parent node. + * - comment-preview: When previewing a new or edited comment. + * The following applies only to viewers who are registered users: + * - comment-unpublished: An unpublished comment visible only to administrators. + * - comment-by-viewer: Comment by the user currently viewing the page. + * - comment-new: New comment since last the visit. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * These two variables are provided for context: + * - $comment: Full comment object. + * - $node: Node object the comments are attached to. + * + * Other variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess() + * @see template_preprocess_comment() + * @see template_process() + * @see theme_comment() + * + * HTML5 notes + * article used for comments nested within node article element + */ +?> +<article class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>> + + <?php print $picture ?> + + <?php if ($new): ?> + <mark class="new"><?php print $new ?></mark> + <?php endif; ?> + + <?php print render($title_prefix); ?> + <h2<?php print $title_attributes; ?>><?php print $title ?></h2> + <?php print render($title_suffix); ?> + + <footer class="submitted"> + <?php print $permalink; ?> + By <?php print $author ?> on <time datetime="<?php print format_date($comment->created, 'custom', 'c'); ?>" pubdate><?php print $created ?></time> + </footer> + + <div class="content"<?php print $content_attributes; ?>> + <?php + // We hide the comments and links now so that we can render them later. + hide($content['links']); + print render($content); + ?> + <?php if ($signature): ?> + <div class="user-signature clearfix"> + <?php print $signature ?> + </div> + <?php endif; ?> + </div> + + <?php print render($content['links']) ?> +</article> diff --git a/mobile/templates/field--body.tpl.php b/mobile/templates/field--body.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..5d03f98343cf8fe24fd01307052ec0f5a2bc7809 --- /dev/null +++ b/mobile/templates/field--body.tpl.php @@ -0,0 +1,47 @@ +<?php + +/** + * @file field.tpl.php + * Default template implementation to display the value of a field. + * + * This file is not used and is here as a starting point for customization only. + * @see theme_field() + * + * Available variables: + * - $items: An array of field values. Use render() to output them. + * - $label: The item label. + * - $label_hidden: Whether the label display is set to 'hidden'. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - field: The current template type, i.e., "theming hook". + * - field-name-[field_name]: The current field name. For example, if the + * field name is "field_description" it would result in + * "field-name-field-description". + * - field-type-[field_type]: The current field type. For example, if the + * field type is "text" it would result in "field-type-text". + * - field-label-[label_display]: The current label position. For example, if + * the label position is "above" it would result in "field-label-above". + * + * Other variables: + * - $element['#object']: The entity to which the field is attached. + * - $element['#view_mode']: View mode, e.g. 'full', 'teaser'... + * - $element['#field_name']: The field name. + * - $element['#field_type']: The field type. + * - $element['#field_language']: The field language. + * - $element['#field_translatable']: Whether the field is translatable or not. + * - $element['#label_display']: Position of label display, inline, above, or + * hidden. + * - $field_name_css: The css-compatible field name. + * - $field_type_css: The css-compatible field type. + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess_field() + * @see theme_field() + * + * Assumption: Simplifying because nobody displays body twice + */ +?> +<?php print render($item); ?> \ No newline at end of file diff --git a/mobile/templates/field--image.tpl.php b/mobile/templates/field--image.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..51729eadd1bc4a52cbb75424b9a0750234b179b6 --- /dev/null +++ b/mobile/templates/field--image.tpl.php @@ -0,0 +1,55 @@ +<?php + +/** + * @file field.tpl.php + * Default template implementation to display the value of a field. + * + * This file is not used and is here as a starting point for customization only. + * @see theme_field() + * + * Available variables: + * - $items: An array of field values. Use render() to output them. + * - $label: The item label. + * - $label_hidden: Whether the label display is set to 'hidden'. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - field: The current template type, i.e., "theming hook". + * - field-name-[field_name]: The current field name. For example, if the + * field name is "field_description" it would result in + * "field-name-field-description". + * - field-type-[field_type]: The current field type. For example, if the + * field type is "text" it would result in "field-type-text". + * - field-label-[label_display]: The current label position. For example, if + * the label position is "above" it would result in "field-label-above". + * + * Other variables: + * - $element['#object']: The entity to which the field is attached. + * - $element['#view_mode']: View mode, e.g. 'full', 'teaser'... + * - $element['#field_name']: The field name. + * - $element['#field_type']: The field type. + * - $element['#field_language']: The field language. + * - $element['#field_translatable']: Whether the field is translatable or not. + * - $element['#label_display']: Position of label display, inline, above, or + * hidden. + * - $field_name_css: The css-compatible field name. + * - $field_type_css: The css-compatible field type. + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess_field() + * @see theme_field() + */ +?> + +<div class="<?php print $classes; ?>"<?php print $attributes; ?>> + <?php if (!$label_hidden): ?> + <div class="field-label"<?php print $title_attributes; ?>><?php print $label ?>: </div> + <?php endif; ?> + <?php foreach ($items as $delta => $item): ?> + <figure class="field-item <?php print $delta % 2 ? 'odd' : 'even'; ?>"<?php print $item_attributes[$delta]; ?>> + <?php print render($item); ?> + </figure> + <?php endforeach; ?> +</div> diff --git a/mobile/templates/field.tpl.php b/mobile/templates/field.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..8d308c0406a0d4db5caff2736c4f845acf16f618 --- /dev/null +++ b/mobile/templates/field.tpl.php @@ -0,0 +1,55 @@ +<?php + +/** + * @file field.tpl.php + * Default template implementation to display the value of a field. + * + * This file is not used and is here as a starting point for customization only. + * @see theme_field() + * + * Available variables: + * - $items: An array of field values. Use render() to output them. + * - $label: The item label. + * - $label_hidden: Whether the label display is set to 'hidden'. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - field: The current template type, i.e., "theming hook". + * - field-name-[field_name]: The current field name. For example, if the + * field name is "field_description" it would result in + * "field-name-field-description". + * - field-type-[field_type]: The current field type. For example, if the + * field type is "text" it would result in "field-type-text". + * - field-label-[label_display]: The current label position. For example, if + * the label position is "above" it would result in "field-label-above". + * + * Other variables: + * - $element['#object']: The entity to which the field is attached. + * - $element['#view_mode']: View mode, e.g. 'full', 'teaser'... + * - $element['#field_name']: The field name. + * - $element['#field_type']: The field type. + * - $element['#field_language']: The field language. + * - $element['#field_translatable']: Whether the field is translatable or not. + * - $element['#label_display']: Position of label display, inline, above, or + * hidden. + * - $field_name_css: The css-compatible field name. + * - $field_type_css: The css-compatible field type. + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess_field() + * @see theme_field() + */ +?> + +<div class="<?php print $classes; ?>"<?php print $attributes; ?>> + <?php if (!$label_hidden): ?> + <div class="field-label"<?php print $title_attributes; ?>><?php print $label ?>: </div> + <?php endif; ?> + <div class="field-items"<?php print $content_attributes; ?>> + <?php foreach ($items as $delta => $item): ?> + <div class="field-item <?php print $delta % 2 ? 'odd' : 'even'; ?>"<?php print $item_attributes[$delta]; ?>><?php print render($item); ?></div> + <?php endforeach; ?> + </div> +</div> diff --git a/mobile/templates/html.tpl.php b/mobile/templates/html.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..2d4769d3882025b5840448f8087aeab8bf866d97 --- /dev/null +++ b/mobile/templates/html.tpl.php @@ -0,0 +1,32 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01+RDFa 1.1//EN" + "http://www.w3.org/MarkUp/DTD/html401-rdfa11-1.dtd"> +<html lang="<?php print $language->language; ?>" dir="<?php print $language->dir; ?>"<?php print $rdf_namespaces; ?>> + +<head profile="<?php print $grddl_profile; ?>"> + <?php print $head; ?> + <title><?php print $head_title; ?></title> + <meta name="viewport" content="width=device-width" /> + <?php print $styles; ?> + <!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]--> +</head> + + <meta name="MobileOptimized" content="width"> + <meta name="HandheldFriendly" content="true"> + <meta name="viewport" content="width=device-width, target-densityDpi=160dpi"> + <meta http-equiv="cleartype" content="on"> + <?php if ($meta_mobilewebapp): ?> + <meta name="apple-mobile-web-app-capable" content="yes"> + <?php endif; ?> + <?php print $styles; ?> + <?php print $scripts; ?> +</head> + +<body class="<?php print $classes; ?>" <?php print $attributes;?>> + <div id="skip-link"> + <a href="#main-content" class="element-invisible element-focusable"><?php print t('Skip to main content'); ?></a> + </div> + <?php print $page_top; ?> + <?php print $page; ?> + <?php print $page_bottom; ?> +</body> +</html> \ No newline at end of file diff --git a/mobile/templates/node.tpl.php b/mobile/templates/node.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..0291ee507a67d82ccd18693d57766658e26c3cfb --- /dev/null +++ b/mobile/templates/node.tpl.php @@ -0,0 +1,112 @@ +<?php + +/** + * @file + * Default theme implementation to display a node. + * + * Available variables: + * - $title: the (sanitized) title of the node. + * - $content: An array of node items. Use render($content) to print them all, + * or print a subset such as render($content['field_example']). Use + * hide($content['field_example']) to temporarily suppress the printing of a + * given element. + * - $user_picture: The node author's picture from user-picture.tpl.php. + * - $date: Formatted creation date. Preprocess functions can reformat it by + * calling format_date() with the desired parameters on the $created variable. + * - $name: Themed username of node author output from theme_username(). + * - $node_url: Direct url of the current node. + * - $display_submitted: Whether submission information should be displayed. + * - $submitted: Submission information created from $name and $date during + * template_preprocess_node(). + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - node: The current template type, i.e., "theming hook". + * - node-[type]: The current node type. For example, if the node is a + * "Blog entry" it would result in "node-blog". Note that the machine + * name will often be in a short form of the human readable label. + * - node-teaser: Nodes in teaser form. + * - node-preview: Nodes in preview mode. + * The following are controlled through the node publishing options. + * - node-promoted: Nodes promoted to the front page. + * - node-sticky: Nodes ordered above other non-sticky nodes in teaser + * listings. + * - node-unpublished: Unpublished nodes visible only to administrators. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Other variables: + * - $node: Full node object. Contains data that may not be safe. + * - $type: Node type, i.e. story, page, blog, etc. + * - $comment_count: Number of comments attached to the node. + * - $uid: User ID of the node author. + * - $created: Time the node was published formatted in Unix timestamp. + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $zebra: Outputs either "even" or "odd". Useful for zebra striping in + * teaser listings. + * - $id: Position of the node. Increments each time it's output. + * + * Node status variables: + * - $view_mode: View mode, e.g. 'full', 'teaser'... + * - $teaser: Flag for the teaser state (shortcut for $view_mode == 'teaser'). + * - $page: Flag for the full page state. + * - $promote: Flag for front page promotion state. + * - $sticky: Flags for sticky post setting. + * - $status: Flag for published status. + * - $comment: State of comment settings for the node. + * - $readmore: Flags true if the teaser content of the node cannot hold the + * main body content. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * + * Field variables: for each field instance attached to the node a corresponding + * variable is defined, e.g. $node->body becomes $body. When needing to access + * a field's raw values, developers/themers are strongly encouraged to use these + * variables. Otherwise they will have to explicitly specify the desired field + * language, e.g. $node->body['en'], thus overriding any language negotiation + * rule that was previously applied. + * + * @see template_preprocess() + * @see template_preprocess_node() + * @see template_process() + * + * HTML5: When h1 printed by page.tpl, article + header elements opened there. + */ +?> + +<?php if (!$page): ?> + <article id="node-<?php print $node->nid; ?> article-<?php print $id; ?>" class="<?php print $classes; ?>" <?php print $attributes; ?>> + <a href="<?php print $node_url; ?>" class="article-title <?php print $type; ?> <?php print $view_mode; ?>"> + <?php print render($title_prefix); ?> + <h1<?php print $title_attributes; ?>><?php print $title; ?></h1> + <?php print render($title_suffix); ?></a> +<?php endif; ?> + +<?php if ($display_submitted): ?> + <footer class="<?php print $type; ?>-<?php print $view_mode; ?> submitted"> + <?php print $user_picture; ?> + <span class="submitted">By <?php print $name; ?>, posted <time pubdate datetime="<?php print format_date($node->created, 'custom', 'c'); ?>"><?php print $date; ?></time></span> + </footer> +<?php endif; ?> + +<?php + // We hide the comments and links now so that we can render them later. + hide($content['comments']); + hide($content['links']); + print render($content); +?> + +<footer class="<?php print $type; ?>-<?php print $view_mode; ?> links"> + <?php print render($content['links']); ?> +</footer> + +<?php print render($content['comments']); ?> + +</article> \ No newline at end of file diff --git a/mobile/templates/page.tpl.php b/mobile/templates/page.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..8f73132e6c7d3a9ac0130e8176e000e748e245f4 --- /dev/null +++ b/mobile/templates/page.tpl.php @@ -0,0 +1,75 @@ +<header role="banner"> + + <?php if ($logo): ?> + <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home" id="logo"> + <img src="<?php print $logo; ?>" alt="<?php print t('Home'); ?>" /> + </a> + <?php endif; ?> + + <hgroup> + <?php if ($site_name): ?> + <h1 id="site-name"> + <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a> + </h1> + <?php endif; ?> + + <?php if ($site_slogan): ?> + <h2 id="site-slogan"><?php print $site_slogan; ?></h2> + <?php endif; ?> + </hgroup> + +</header> + +<?php print $messages; ?> + +<main role="main" id="main-content"> + + <?php if ($page['highlighted']): ?><div id="highlighted"><?php print render($page['highlighted']); ?></div><?php endif; ?> + + <?php if ($tabs): ?><div class="tabs"><?php print render($tabs); ?></div><?php endif; ?> + + <a id="main-content"></a> + + <?php if ($title): ?> + <?php + // open article tag if page is a node + if (($page) && (arg(0) == 'node')): ?> + <article role="article"> + <?php endif; ?> + <?php print render($title_prefix); ?> + <h1 class="title" id="page-title"><?php print $title; ?></h1> + <?php endif; ?> + <?php print render($title_suffix); ?> + + <?php print render($page['help']); ?> + + <?php if ($action_links): ?><nav><ul class="action-links"><?php print render($action_links); ?></ul></nav><?php endif; ?> + + <?php print render($page['content']); ?> + + <?php if ($breadcrumb): ?> + <nav id="breadcrumb"><?php print $breadcrumb; ?></nav> + <?php endif; ?> + +</main><!--/main --> + +<?php if ($page['supplementary']): ?> + <aside id="supplementary" class="column sidebar" role="complementary"> + <?php print render($page['supplementary']); ?> +<?php endif; ?> + +<?php if ($main_menu || $secondary_menu): ?> + <nav role="navigation"> + + <?php print theme('links__system_main_menu', array('links' => $main_menu, 'attributes' => array('id' => 'main-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Main menu'))); ?> + + <?php print theme('links__system_secondary_menu', array('links' => $secondary_menu, 'attributes' => array('id' => 'secondary-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Secondary menu'))); ?> + + </nav> +<?php endif; ?> + + <?php if ($page['footer']): ?> + <footer role="contentinfo"> + <?php print render($page['footer']); ?> + </footer> + <?php endif; ?> \ No newline at end of file diff --git a/mobile/templates/region--content.tpl.php b/mobile/templates/region--content.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..1e61063d6e560fc92e01bfd4afad5d6417af2294 --- /dev/null +++ b/mobile/templates/region--content.tpl.php @@ -0,0 +1,31 @@ +<?php + +/** + * @file + * Default theme implementation to display a region. + * + * Available variables: + * - $content: The content for this region, typically blocks. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the following: + * - region: The current template type, i.e., "theming hook". + * - region-[name]: The name of the region with underscores replaced with + * dashes. For example, the page_top region would have a region-page-top class. + * - $region: The name of the region variable as defined in the theme's .info file. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $is_admin: Flags true when the current user is an administrator. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * + * @see template_preprocess() + * @see template_preprocess_region() + * @see template_process() + */ +?> +<?php if ($content): ?> + <?php print $content; ?> +<?php endif; ?> diff --git a/mobile/templates/region--footer.tpl.php b/mobile/templates/region--footer.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..dce3b066ea8c33303355d5a85abf19deb1bb24e7 --- /dev/null +++ b/mobile/templates/region--footer.tpl.php @@ -0,0 +1,33 @@ +<?php + +/** + * @file + * Default theme implementation to display a region. + * + * Available variables: + * - $content: The content for this region, typically blocks. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the following: + * - region: The current template type, i.e., "theming hook". + * - region-[name]: The name of the region with underscores replaced with + * dashes. For example, the page_top region would have a region-page-top class. + * - $region: The name of the region variable as defined in the theme's .info file. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $is_admin: Flags true when the current user is an administrator. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * + * @see template_preprocess() + * @see template_preprocess_region() + * @see template_process() + */ +?> +<?php if ($content): ?> + <footer role="contentinfo"> + <?php print $content; ?> + </footer> +<?php endif; ?> diff --git a/mobile/templates/region--supplementary.tpl.php b/mobile/templates/region--supplementary.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..1e61063d6e560fc92e01bfd4afad5d6417af2294 --- /dev/null +++ b/mobile/templates/region--supplementary.tpl.php @@ -0,0 +1,31 @@ +<?php + +/** + * @file + * Default theme implementation to display a region. + * + * Available variables: + * - $content: The content for this region, typically blocks. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the following: + * - region: The current template type, i.e., "theming hook". + * - region-[name]: The name of the region with underscores replaced with + * dashes. For example, the page_top region would have a region-page-top class. + * - $region: The name of the region variable as defined in the theme's .info file. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $is_admin: Flags true when the current user is an administrator. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * + * @see template_preprocess() + * @see template_preprocess_region() + * @see template_process() + */ +?> +<?php if ($content): ?> + <?php print $content; ?> +<?php endif; ?> diff --git a/mobile/templates/region.tpl.php b/mobile/templates/region.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..b29e24f0178f56318ed3415a6c06e40f42786618 --- /dev/null +++ b/mobile/templates/region.tpl.php @@ -0,0 +1,33 @@ +<?php + +/** + * @file + * Default theme implementation to display a region. + * + * Available variables: + * - $content: The content for this region, typically blocks. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the following: + * - region: The current template type, i.e., "theming hook". + * - region-[name]: The name of the region with underscores replaced with + * dashes. For example, the page_top region would have a region-page-top class. + * - $region: The name of the region variable as defined in the theme's .info file. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $is_admin: Flags true when the current user is an administrator. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * + * @see template_preprocess() + * @see template_preprocess_region() + * @see template_process() + */ +?> +<?php if ($content): ?> + <div class="<?php print $classes; ?>"> + <?php print $content; ?> + </div> +<?php endif; ?> diff --git a/mobile/templates/search-block-form.tpl.php b/mobile/templates/search-block-form.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..da58403c2c4709e7b9d30cd25220f36a026a553d --- /dev/null +++ b/mobile/templates/search-block-form.tpl.php @@ -0,0 +1,37 @@ +<?php + +/** + * @file + * Displays the search form block. + * + * Available variables: + * - $search_form: The complete search form ready for print. + * - $search: Associative array of search elements. Can be used to print each + * form element separately. + * + * Default elements within $search: + * - $search['search_block_form']: Text input area wrapped in a div. + * - $search['actions']: Rendered form buttons. + * - $search['hidden']: Hidden form elements. Used to validate forms when + * submitted. + * + * Modules can add to the search form, so it is recommended to check for their + * existence before printing. The default keys will always exist. To check for + * a module-provided field, use code like this: + * @code + * <?php if (isset($search['extra_field'])): ?> + * <div class="extra-field"> + * <?php print $search['extra_field']; ?> + * </div> + * <?php endif; ?> + * @endcode + * + * @see template_preprocess_search_block_form() + */ +?> +<div class="container-inline"> + <?php if (empty($variables['form']['#block']->subject)): ?> + <h2 class="element-invisible"><?php print t('Search form'); ?></h2> + <?php endif; ?> + <?php print $search_form; ?> +</div> diff --git a/mobile/templates/views-view--page.tpl.php b/mobile/templates/views-view--page.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..36b2a99a7e9f54caec701a2f999a6a14b1df42b4 --- /dev/null +++ b/mobile/templates/views-view--page.tpl.php @@ -0,0 +1,88 @@ +<?php + +/** + * @file + * Main view template. + * + * Variables available: + * - $classes_array: An array of classes determined in + * template_preprocess_views_view(). Default classes are: + * .view + * .view-[css_name] + * .view-id-[view_name] + * .view-display-id-[display_name] + * .view-dom-id-[dom_id] + * - $classes: A string version of $classes_array for use in the class attribute + * - $css_name: A css-safe version of the view name. + * - $css_class: The user-specified classes names, if any + * - $header: The view header + * - $footer: The view footer + * - $rows: The results of the view query, if any + * - $empty: The empty text to display if the view is empty + * - $pager: The pager next/prev links to display, if any + * - $exposed: Exposed widget form/info to display + * - $feed_icon: Feed icon to display, if any + * - $more: A link to view more, if any + * + * @ingroup views_templates + */ +?> +<div class="<?php print $classes; ?>"> + <?php print render($title_prefix); ?> + <?php if ($title): ?> + <?php print $title; ?> + <?php endif; ?> + <?php print render($title_suffix); ?> + <?php if ($header): ?> + <div class="view-header"> + <?php print $header; ?> + </div> + <?php endif; ?> + + <?php if ($exposed): ?> + <div class="view-filters"> + <?php print $exposed; ?> + </div> + <?php endif; ?> + + <?php if ($attachment_before): ?> + <div class="attachment attachment-before"> + <?php print $attachment_before; ?> + </div> + <?php endif; ?> + + <?php if ($rows): ?> + <?php print $rows; ?> + <?php elseif ($empty): ?> + <div class="view-empty"> + <?php print $empty; ?> + </div> + <?php endif; ?> + + <?php if ($pager): ?> + <?php print $pager; ?> + <?php endif; ?> + + <?php if ($attachment_after): ?> + <div class="attachment attachment-after"> + <?php print $attachment_after; ?> + </div> + <?php endif; ?> + + <?php if ($more): ?> + <?php print $more; ?> + <?php endif; ?> + + <?php if ($footer): ?> + <div class="view-footer"> + <?php print $footer; ?> + </div> + <?php endif; ?> + + <?php if ($feed_icon): ?> + <div class="feed-icon"> + <?php print $feed_icon; ?> + </div> + <?php endif; ?> + +</div><?php /* class view */ ?> \ No newline at end of file diff --git a/mobile/templates/views-view-unformatted--page.tpl.php b/mobile/templates/views-view-unformatted--page.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..f29a8d173293515141d5dce87a73680e8cbc896e --- /dev/null +++ b/mobile/templates/views-view-unformatted--page.tpl.php @@ -0,0 +1,12 @@ +<?php + +/** + * Remove extraneous divs + */ +?> +<?php if (!empty($title)): ?> + <h3><?php print $title; ?></h3> +<?php endif; ?> +<?php foreach ($rows as $id => $row): ?> + <?php print $row; ?> +<?php endforeach; ?> \ No newline at end of file diff --git a/mobile/theme-settings.php b/mobile/theme-settings.php new file mode 100644 index 0000000000000000000000000000000000000000..ab2d0fcf2dc090954af493dcec0daa577145b9c1 --- /dev/null +++ b/mobile/theme-settings.php @@ -0,0 +1,23 @@ +<?php +// Responsive design support settings +function mobile_form_system_theme_settings_alter(&$form, &$form_state) { + // Workaround for core bug http://drupal.org/node/943212 affecting admin themes. + if (isset($form_id)) { + return; + } + +// Fieldgroup: Browser support widget settings +$form['html5_browser_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Web App Mode'), + '#weight' => '-45', +); + + $form['html5_browser_settings']['mobilewebapp'] = array( + '#type' => 'checkbox', + '#title' => t('Allow iOS Safari to operate in "mobile web app" mode.'), + '#default_value' => theme_get_setting('mobilewebapp'), + '#description' => t('Set the browser on iOS to run in full-screen mode. Be sure you want to do this! This setting will hide browser chrome in iOS. For more info, read <a href="!link">Apple Developer documentation on Apple-Specific Meta Tag Keys</a>.', array('!link' => 'https://developer.apple.com/library/safari/#documentation/appleapplications/reference/SafariHTMLRef/Articles/MetaTags.html')), + ); + +} diff --git a/my_theme/qtici_contextual_entity_template.tpl.php b/my_theme/qtici_contextual_entity_template.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..55379188506866bda8a36d60661cdcad7cc7c638 --- /dev/null +++ b/my_theme/qtici_contextual_entity_template.tpl.php @@ -0,0 +1,9 @@ +<div class="contextual-links-wrapper"> + <ul class="contextual-links"> + <?php foreach($links as $link): ?> + <li> + <?php echo l($link[1], $link[0]) ?> + </li> + <?php endforeach; ?> + </ul> +</div> \ No newline at end of file diff --git a/my_theme/qtici_item_entity_template.tpl.php b/my_theme/qtici_item_entity_template.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..e0ace55d408336c540fe169158e73f15acb7cb8c --- /dev/null +++ b/my_theme/qtici_item_entity_template.tpl.php @@ -0,0 +1,10 @@ +<?php +$content = $element; +?> + +<?php print $content["form"]; ?> + +<?php +if (current_path() == "qtici/item/" . $content["itemid"]) + print '<p>' . l(t("Terug naar test"), 'qtici/test/' . $_SESSION['entity_test']['testid']) . '</p>'; + ?> diff --git a/my_theme/qtici_test_entity_template.tpl.php b/my_theme/qtici_test_entity_template.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..e914768e86e7f57b088d0361b8690579c5a52dd4 --- /dev/null +++ b/my_theme/qtici_test_entity_template.tpl.php @@ -0,0 +1,18 @@ +<?php +$content = $element; +?> + +<p> + <?php if (!empty($content["time"])) { + print $content["time"] + ?> + <span id="cdtime">loading countdown...</span> +<?php } ?> +</p> + +<p> + <?php print($content["statisticLink"]) ?> +</p> +<div> +<?php print $content["form"] ?> +</div> diff --git a/objectParserv3.php b/objectParserv3.php new file mode 100755 index 0000000000000000000000000000000000000000..e7dc1f83e4ff74cfbf2e2c9cc66ae41af5f02770 --- /dev/null +++ b/objectParserv3.php @@ -0,0 +1,160 @@ +<?php + +ini_set('display_errors', 1); + +/** + * + * This script converts a QTI test from OLAT to an object. + * + * The QTI elements supported are: Single Choice Questions, Multiple Choice + * Questions, Fill In Questions and KPRIM-Questions + * + * Joey Lemmens + * + */ +function getDataIfExists() { + // We accept an unknown number of arguments + $args = func_get_args(); + + if (!count($args)) { + trigger_error('getDataIfExists() expects a minimum of 1 argument', E_USER_WARNING); + return NULL; + } + + // The object we are working with + $baseObj = array_shift($args); + + // Check it actually is an object + if (!is_object($baseObj)) { + trigger_error('getDataIfExists(): first argument must be an object', E_USER_WARNING); + return NULL; + } + + // Loop subsequent arguments, check they are valid and get their value(s) + foreach ($args as $arg) { + $arg = $arg; + if (substr($arg, -2) == '()') { // method + $arg = substr($arg, 0, -2); + if (!method_exists($baseObj, $arg)) + return NULL; + $baseObj = $baseObj->$arg(); + } else { // property + if (!isset($baseObj->$arg)) + return NULL; + $baseObj = $baseObj->$arg; + } + } + // If we get here $baseObj will contain the item referenced by the supplied chain + return $baseObj; +} + +function getQuestionType($input) { + $length = (strrpos($input, ':') - 1) - strpos($input, ':'); + return substr($input, strpos($input, ':') + 1, $length); +} + +// Quotation for FIB or MCQ can be either allCorrect or perAnswer +// Function returns also the results form xpath! +function getQuotationType($item) { + // XML structure is different when quatation is different (ALL/PER correct answer) + $results = $item->xpath('resprocessing/respcondition[setvar and not(conditionvar/other)]'); + if (count($results[0]->conditionvar->and) > 0) { + $quotation = 'allCorrect'; + } else { + $quotation = 'perAnswer'; + } + + return array('quotation' => $quotation, + 'results' => $results + ); +} + +// Function for fetching Feedback, Hints & SolutionFeedback +function qtici_fetchFeedback(&$object, $item) { + $hint = $item->xpath('itemfeedback/hint'); + $object->setHint(isset($hint[0]->hintmaterial->material->mattext) ? (string) $hint[0]->hintmaterial->material->mattext : null); + $solutionFeedback = $item->xpath('itemfeedback/solution'); + $object->setSolutionFeedback(isset($solutionFeedback[0]->solutionmaterial->material->mattext) ? (string) $solutionFeedback[0]->solutionmaterial->material->mattext : null); + + $feedbackitems = $item->xpath('itemfeedback[material[1]]'); + foreach ($feedbackitems as $feedbackitem) { + $feedbackObject = new Feedback((string) $feedbackitem->attributes()->ident, (string) $feedbackitem->material->mattext + ); + $object->setFeedback($feedbackObject); + } +} + +function parseQTIToObject($filename, $folderID) { + + if (file_exists($filename)) { + $xml = new SimpleXMLElement($filename, null, true); + } else { + exit('Failed to open ' . $filename); + } + + $sections = $xml->assessment->section; + $categories = array(); + + $testDescription = _qtici_get_testDescription((string) getDataIfExists($xml, 'assessment', 'objectives', 'material', 'mattext'), $categories); + + $test_values = array( + 'title' => (string) getDataIfExists($xml, 'assessment', 'attributes()', 'title'), + 'description' => $testDescription, + 'duration' => (string) getDataIfExists($xml, 'assessment', 'duration'), + 'passing_score' => (string) getDataIfExists($xml, 'assessment', 'outcomes_processing', 'outcomes', 'decvar', 'attributes()', 'cutvalue'), + // Only one bundle for now + 'bundle' => 'qtici_test', + ); + + $testObject = entity_create('qtici_test', $test_values); + $testObject->saveCategories($categories); + + // Loop through each section + foreach ($sections as $section) { + + $sectionObject = new Section((string) getDataIfExists($section, 'attributes()', 'ident'), (string) getDataIfExists($section, 'attributes()', 'title'), (string) getDataIfExists($section, 'objectives', 'material', 'mattext'), (string) getDataIfExists($section, 'selection_ordering', 'order', 'attributes()', 'order_type')); + + $testObject->setSection($sectionObject); + + // Loop through each item + $items = getDataIfExists($section, 'item'); + + foreach ($items as $item) { + + // Each question type has be treated differently + $questionType = getQuestionType($item->attributes()->ident); + $QObject = entity_create('qtici_' . $questionType, array()); + $QObject->parseXML($item); + + // Replace video and audio in possibilities + $possibilities = $QObject->getPossibilities(); + $QObject->deletePossibilities(); + foreach ($possibilities as $poss) { + //watchdog(WATCHDOG_DEBUG, var_dump($poss)); + $answer = unserialize($poss->getAnswer()); + $newVal = checkIfExistAndCast($answer['value'], $filename, $folderID); + $answer['value'] = $newVal; + $poss->setAnswer(serialize($answer)); + //watchdog(WATCHDOG_DEBUG, var_dump($poss)); + $QObject->setPossibility($poss); + } + + $question = (string) getDataIfExists($item, 'presentation', 'material', 'mattext'); + if ($questionType === 'FIB') { + // For FIB + $question = (string) getDataIfExists($item, 'presentation', 'flow', 'material', 'mattext'); + $content = unserialize($QObject->content); + $content = checkIfExistAndCast($content, $filename, $folderID); + $QObject->setContent($content); + } + $question2 = checkIfExistAndCast($question, $filename, $folderID); + $QObject->setQuestion($question2); + + $sectionObject->setItem($QObject); + } + } + + return $testObject; +} + +?> diff --git a/qtici.info b/qtici.info new file mode 100755 index 0000000000000000000000000000000000000000..7fc58b3e12de52e11da8de09d9985d42142b2360 --- /dev/null +++ b/qtici.info @@ -0,0 +1,32 @@ +name = QTI Test and Course Import +description = This module imports QTI 1.2 courses and tests, and enables extra functionality. +core = 7.x +package = QTICI + +files[] = qtici.install + +files[] = classes/Possibility.php +files[] = classes/Feedback.php +files[] = classes/Item.php +files[] = classes/Statistic.php +files[] = classes/Test.php +files[] = classes/ElementTypes.php +files[] = classes/exercises/SingleChoiceQuestion.php +files[] = classes/exercises/FillInQuestion.php +files[] = classes/exercises/MultipleChoiceQuestion.php +files[] = classes/exercises/KPRIMQuestion.php +files[] = classes/exercises/MarkerQuestion.php +files[] = classes/exercises/VideoQuestion.php +files[] = classes/exercises/DragAndDropQuestion.php +files[] = classes/exercises/RecorderQuestion.php +files[] = classes/Section.php +files[] = classes/Item.php +files[] = qtici_tests.test +files[] = handlers/views_handler_field_description.inc + +dependencies[] = jquery_update +dependencies[] = entity +dependencies[] = views +dependencies[] = taxonomy +dependencies[] = taxonomy_entity_index +dependencies[] = search_api diff --git a/qtici.install b/qtici.install new file mode 100755 index 0000000000000000000000000000000000000000..e1959319c041fb9da675d693db4c242e387e874e --- /dev/null +++ b/qtici.install @@ -0,0 +1,506 @@ +<?php + +/* + * @file + * Install file of Dutch++ + * + * Makes the database with 15 tables which can be split up into two schemas. + * One for courses and the other for the exercices. + */ + +/** + * Implements hook_schema(). + */ +function qtici_schema() { + $schema = array(); + + $schema['qtici_test'] = array( + 'description' => t('Contains the test.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', // incremental + 'unsigned' => TRUE, // no negative integers + 'size' => 'normal', // tiny, small, medium, normal, big + 'not null' => TRUE, // default FALSE + 'description' => t('The primary identifier for a test.'), + ), + 'bundle' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('The type of bundle'), + ), + 'olat_testid' => array(// Needs to be text, if we want it as an index needs to be varchar + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => t('Contains the id given by OLAT, the course will only this ID (unique)'), + ), + 'title' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the title of the test.'), + ), + 'description' => array( + 'type' => 'blob', + 'size' => 'normal', + 'description' => t('Contains the description or objectives of the test.'), + ), + 'duration' => array(// NOT REQUIRED + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the duration of how long the test takes.'), + ), + 'passing_score' => array(// NOT REQUIRED + 'type' => 'float', + 'size' => 'normal', + 'description' => t('Contains the score to pass the test.'), + ), + 'published' => array(// NOT REQUIRED + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'default' => 1, + 'description' => t('Contains TRUE = 1 if the test is published and FALSE = 0 if not.'), + ), + 'answers_in' => array(// NOT REQUIRED + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'default' => 1, + 'description' => t('Contains TRUE = 1 if the answers (and feedback) are displayed in the test and FALSE = 0 if not.'), + ), + 'answers_out' => array(// NOT REQUIRED + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'default' => 1, + 'description' => t('Contains TRUE = 1 if the answers (and feedback) are displayed after processing the form and FALSE = 0 if not.'), + ), + 'show_answer' => array(// NOT REQUIRED + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'default' => 1, + 'description' => t('Contains TRUE = 1 if the button show answer is displayed in the test and FALSE = 0 if not.'), + ), + 'check_answer' => array(// NOT REQUIRED + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'default' => 1, + 'description' => t('Contains TRUE = 1 if the button check answer is displayed after processing the form and FALSE = 0 if not.'), + ), + 'date' => array( + 'type' => 'int', + 'size' => 'normal', + 'not null' => FALSE, + 'description' => t('Contains the date when a test is uploaded.'), + ), + 'course' => array( + 'type' => 'text', + 'size' => 'normal', + 'not null' => FALSE, + 'description' => t('Contains the course to which a test belongs.'), + ), + 'topic' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Keyword for ordering the tests by topic'), + ), + 'level' => array( + 'type' => 'text', + 'size' => 'small', + 'description' => t('Keyword for ordering the tests by level (A, B or C)'), + ), + ), + 'indexes' => array( + 'olat_testid' => array('olat_testid'), + ), + 'primary key' => array('id'), + ); + + $schema['qtici_section'] = array( + 'description' => t('Contains the sections within a test.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t('The primary identifier for a section.'), + ), + 'testid' => array( + 'type' => 'int', + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t('FK to qti_test.id.') + ), + 'title' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the title of the section.'), + ), + 'description' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the description or objectives of the section.'), + ), + 'ordering' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains whether items in the section should be ordered sequential or random.'), + ), + ), + 'indexes' => array( + 'testid' => array('testid'), + ), + 'foreign keys' => array( + 'fk_qti_section_to_test' => array( + 'table' => 'qti_test', + 'columns' => array('testid' => 'id'), + ), + ), + 'primary key' => array('id'), + ); + + $schema['qtici_item'] = array( + 'description' => t('Contains the items within a section.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t('The primary identifier for an item.'), + ), + 'sectionid' => array( + 'type' => 'int', + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t('FK to qti_section.id.') + ), + 'title' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the title of the item.'), + ), + 'description' => array(// NOT REQUIRED + 'type' => 'blob', + 'size' => 'normal', + 'description' => t('Contains the description or objectives of the item.'), + ), + 'type' => array( + 'type' => 'text', + 'size' => 'medium', + 'description' => t('Contains the type of question: SCQ, MCQ, FIB, KPRIM.'), + ), + 'quotation' => array(// NOT REQUIRED + 'type' => 'text', + 'size' => 'medium', + 'description' => t('Contains how the quotation should happen, score per answer or all correct.'), + ), + 'question' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the question.'), + ), + 'max_attempts' => array(// NOT REQUIRED + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'normal', + 'description' => t('Contains how many times the item can be filled in.'), + ), + 'ordering' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'description' => t('Contains TRUE = 1 if items should be ordered random and FALSE = 0 if not.'), + ), + 'score' => array(// NOT REQUIRED + 'type' => 'float', + 'size' => 'normal', + 'description' => t('Contains the score if quotation = all correct.'), + ), + 'content' => array( + 'type' => 'blob', + 'size' => 'normal', + 'not null' => FALSE, + 'description' => t('Contains the content of the item: text + format'), + ), + ), + 'indexes' => array( + 'sectionid' => array('sectionid'), + ), + 'foreign keys' => array( + 'fk_qti_item_to_section' => array( + 'table' => 'qti_section', + 'columns' => array('sectionid' => 'id'), + ), + ), + 'primary key' => array('id'), + ); + + $schema['qtici_possibility'] = array( + 'description' => t('Contains the possibilities within an item.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t('The primary identifier for a possibility.'), + ), + 'itemid' => array( + 'type' => 'int', + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t('FK to qti_item.id.') + ), + 'possibility' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains a possibility: radio, textbox, checkbox, etc'), + ), + 'is_correct' => array(// NOT REQUIRED + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'description' => t('Contains TRUE = 1 if possibility is correct or FALSE = 0 if not. This will only be used if item.type = SCQ, MCQ or KPRIM.'), + ), + 'answer' => array(// NOT REQUIRED + 'type' => 'blob', + 'size' => 'normal', + 'description' => t('Contains the text of the possibility'), + ), + 'ordering' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'normal', + 'description' => t('Contains the order of which the FIB should be displayed.'), + ), + 'score' => array(// NOT REQUIRED + 'type' => 'float', + 'size' => 'normal', + 'description' => t('Contains the score if item.quotation = score per answer.'), + ), + ), + 'indexes' => array( + 'itemid' => array('itemid'), + ), + 'foreign keys' => array( + 'fk_qti_possibility_to_item' => array( + 'table' => 'qti_item', + 'columns' => array('itemid' => 'id'), + ), + ), + 'primary key' => array('id'), + ); + + $schema['qtici_feedback'] = array( + 'description' => t('Contains the feedback within an item or possibility.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t('The primary identifier for a feedback.'), + ), + 'itemid' => array(// OR itemid is filled in... + 'type' => 'int', + 'size' => 'normal', + 'description' => t('FK to qti_item.id.') + ), + 'possibilityid' => array(// ... or possibilityid is filled in + 'type' => 'int', + 'size' => 'normal', + 'description' => t('FK to qti_possibility.id.') + ), + 'feedback_possibility' => array(// NOT REQUIRED + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the feedback for a certain possibility'), + ), + 'feedback_positive' => array(// NOT REQUIRED + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the possitive (MASTERY) feedback.'), + ), + 'feedback_negative' => array(// NOT REQUIRED + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the negative (FAIL) feedback.'), + ), + 'hint' => array(// NOT REQUIRED + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains a hint.'), + ), + 'solution_feedback' => array(// NOT REQUIRED + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the solution as feedback.'), + ), + ), + 'indexes' => array( + 'itemid' => array('itemid'), + 'possibilityid' => array('possibilityid'), + ), + 'foreign keys' => array( + 'fk_qti_feedback_to_item' => array( + 'table' => 'qti_item', + 'columns' => array('itemid' => 'id'), + ), + 'fk_qti_feedback_to_possibility' => array( + 'table' => 'qti_possibility', + 'columns' => array('possibilityid' => 'id'), + ), + ), + 'primary key' => array('id'), + ); + + $schema['qtici_test_statistics'] = array( + 'description' => t('Contains the statistics about a test'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t('The primary identifier for a teststatistic.'), + ), + 'testid' => array( + 'type' => 'int', + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t('FK to qti_test.id.') + ), + 'completed' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'description' => t('Contains if the test has been completed.'), + ), + 'time_spended' => array( + 'type' => 'text', + 'size' => 'normal', + 'description' => t('Contains the time spended to complete a test.') + ), + 'score' => array( + 'type' => 'float', + 'size' => 'normal', + 'description' => t('Contains the score of a test.') + ), + 'date_started' => array( + 'type' => 'int', + 'size' => 'normal', + 'description' => t('Contains the date when a test is started.'), + ), + ), + 'indexes' => array( + 'testid' => array('testid'), + ), + 'foreign keys' => array( + 'fk_qti_test_statistics_to_test' => array( + 'table' => 'qti_test', + 'columns' => array('testid' => 'id'), + ), + ), + 'primary key' => array('id'), + ); + + return $schema; +} + +/** + * Implements hook_install(). + */ +function qtici_install() { + //Creating new roles + // Modified by Elisa: if they don't exist already!!! + $result = db_query("SELECT * FROM {role} WHERE name = 'teacher' OR name = 'student'"); + $num = $result->rowCount(); + + if ($num == 0) { + $role = new stdClass(); + $role->name = 'teacher'; + $role->weight = 10; + user_role_save($role); + + $role = new stdClass(); + $role->name = 'student'; + $role->weight = 11; + user_role_save($role); + + //Assign permissions to this role + $editor_role = user_role_load_by_name('teacher'); + $editor_rid_teach = $editor_role->rid; + // Define our 'teacher' role permissions + $editor_permissions = array( + 'teacher' => TRUE, // Grant permission + ); + user_role_change_permissions($editor_rid_teach, $editor_permissions); + + //Assign permissions to this role + $editor_role = user_role_load_by_name('student'); + $editor_rid_stu = $editor_role->rid; + // Define our 'teacher' role permissions + $editor_permissions = array( + 'student' => TRUE, // Grant permission + ); + user_role_change_permissions($editor_rid_stu, $editor_permissions); + + //Create users for development purposes + require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); + + $new_user_roles = array( + DRUPAL_AUTHENTICATED_RID => 'authenticated user', + $editor_rid_teach => TRUE, + ); + } + else { + drupal_set_message(st('Roles were not created because of inconsistencies with already existing roles. Please create the necessary roles manually.')); + } +} + +/** + * Implements hook_uninstall(). + */ +function qtici_uninstall() { + _qtici_deleteDirectory('sites/default/files/qtici'); +} + +/** + * Implements hook_disable + */ +function qtici_disable() { + +} + +function qtici_enable() { + // Check if files directory doesn't exists + $dir = 'sites/default/files/qtici'; + if (!file_prepare_directory($dir)) { + drupal_mkdir($dir); + } +} + +/** + * Recursive delete function for directories + */ +function _qtici_deleteDirectory($dir) { + + if (!file_exists($dir)) + return true; + + if (!is_dir($dir)) + return unlink($dir); + + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') + continue; + if (!_qtici_deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) + return false; + } + return drupal_rmdir($dir); +} diff --git a/qtici.module b/qtici.module new file mode 100755 index 0000000000000000000000000000000000000000..12afa22b8b46763f8cff3c0ffda7e7808451abb3 --- /dev/null +++ b/qtici.module @@ -0,0 +1,1049 @@ +<?php + +/* + * @file + * Module file of Dutch++. Only hooks, access, blocks and menus functions. + */ + +module_load_include('inc', 'qtici', 'globals'); +module_load_include('inc', 'qtici', 'functions'); +module_load_include('inc', 'qtici', 'forms'); +module_load_include('inc', 'qtici', 'views'); +module_load_include('inc', 'qtici', 'ajax_callbacks'); +module_load_include('inc', 'qtici', 'uploads'); +module_load_include('inc', 'qtici', 'qtici.taxonomy'); +module_load_include('php', 'qtici', 'qtici_item_form'); + +/** + * Implements hook_views_api(). + */ +function qtici_views_api() { + return array( + 'api' => 3, + 'path' => drupal_get_path('module', 'qtici'), + ); +} + +/** + * Implements hook_entity_info(); + */ +function qtici_entity_info() { + $info['qtici_item'] = array( + 'label' => t('Item'), + 'controller class' => 'EntityAPIController', + 'base table' => 'qtici_item', + 'entity keys' => array( + 'id' => 'id', + ), + 'entity class' => 'Item', + 'static cache' => TRUE, + 'bundles' => array(), + 'load hook' => 'qtici_item_entity_load', + 'module' => 'qtici', + ); + // Trying with a different entity for each exercise + global $_qtici_exercisesClasses; + foreach ($_qtici_exercisesClasses as $key => $value) { + $info['qtici_' . $key] = array( + 'label' => $key, + 'controller class' => 'EntityAPIController', + 'base table' => 'qtici_item', + 'entity keys' => array( + 'id' => 'id', + ), + 'entity class' => $value, + 'static cache' => TRUE, + 'bundles' => array(), + 'load hook' => 'qtici_itemType_entity_load', + 'module' => 'qtici', + ); + } + + $info['qtici_possibility'] = array( + 'label' => t('Possibility'), + 'controller class' => 'EntityAPIController', + 'base table' => 'qtici_possibility', + 'entity keys' => array( + 'id' => 'id', + ), + 'entity class' => 'Possibility', + 'static cache' => TRUE, + 'bundles' => array(), + 'load hook' => 'qtici_possibility_entity_load', + 'module' => 'qtici', + ); + + $info['qtici_test'] = array( + 'label' => t('Test'), + 'controller class' => 'EntityAPIControllerExportable', + 'base table' => 'qtici_test', + 'entity keys' => array( + 'id' => 'id', + 'bundle' => 'bundle', + ), + 'entity class' => 'Test', + 'static cache' => TRUE, + 'bundle keys' => array( + 'bundle' => 'bundle', + ), + 'bundles' => array( + 'qtici_test' => array( + 'label' => 'QTICI Simple Test', + 'admin' => array( + 'path' => 'admin/structure/tests/qtici/manage', + 'access arguments' => array('teacher'), + ), + ), + ), + 'load hook' => 'qtici_test_entity_load', + 'fieldable' => TRUE, + 'module' => 'qtici', + ); + + return $info; +} + +/** + * Load single item entity + */ +function qtici_item_entity_load($id, $reset = FALSE) { + $basic = qtici_item_entity_load_multiple(array($id), array(), $reset); + return reset($basic); +} + +/** + * Load multiple item entities + */ +function qtici_item_entity_load_multiple($item_ids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('qtici_item', $item_ids, $conditions, $reset); +} + +/** + * Load single exercise entity + */ +function qtici_itemType_entity_load($id, $reset = FALSE) { + $item = qtici_item_entity_load($id, $reset); + $basic = entity_load('qtici_' . $item->type, array($id), array(), $reset); + return reset($basic); +} + +/** + * Load single test entity + */ +function qtici_test_entity_load($test_id, $reset = FALSE) { + $basic = qtici_test_entity_load_multiple(array($test_id), array(), $reset); + return reset($basic); +} + +/** + * Load multiple test entities + */ +function qtici_test_entity_load_multiple($test_ids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('qtici_test', $test_ids, $conditions, $reset); +} + +/** + * Load single test entity + */ +function qtici_possibility_entity_load($id, $reset = FALSE) { + $basic = qtici_possibility_entity_load_multiple(array($id), array(), $reset); + return reset($basic); +} + +/** + * Load multiple test entities + */ +function qtici_possibility_entity_load_multiple($ids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('qtici_possibility', $ids, $conditions, $reset); +} + +function qtici_item_entity_admin_page() { + $content = array(); + $content[] = array( + '#type' => 'item', + '#markup' => t('Administration page for Test Entities.') + ); + + $content[] = array( + '#type' => 'item', + '#markup' => l(t('Add a item entity'), 'qtici/item/add'), + ); + + $content['table'] = qtici_item_entity_list_entities(); + + return $content; +} + +function qtici_test_entity_admin_page() { + $form = drupal_get_form("qtici_pageOverview_form"); + $output = drupal_render($form); + + return $output; +} + +function qtici_item_entity_title($entity) { + return t('@title', array('@title' => $entity->title)); +} + +function qtici_test_entity_title($entity) { + return t('@title', array('@title' => $entity->title)); +} + +function qtici_item_entity_view($entity, $view_mode = 'tweaky') { + $item = qtici_itemType_entity_load($entity->id); + return qtici_itemType_entity_view($item, $view_mode = 'tweaky'); +} + +function qtici_itemType_entity_view($entity, $view_mode = 'tweaky') { + + // Our entity type, for convenience. + $entity_type = 'qtici_' . $entity->type; + + // Start setting up the content. + $entity->contentEntity = array( + '#view_mode' => $view_mode, + ); + // Build fields content - this is where the Field API really comes in to play. + // The task has very little code here because it all gets taken care of by + // field module. + // field_attach_prepare_view() lets the fields load any data they need + // before viewing. + field_attach_prepare_view($entity_type, array($entity->id => $entity), $view_mode); + // We call entity_prepare_view() so it can invoke hook_entity_prepare_view() + // for us. + entity_prepare_view($entity_type, array($entity->id => $entity)); + // Now field_attach_view() generates the content for the fields. + $entity->contentEntity += field_attach_view($entity_type, $entity, $view_mode); + + //lay-out configuration for mobile devices + if (module_exists('mobile_theme')) { + + //hide left sidebar when mobile + drupal_add_css('#sidebar-first {display:none;}', $option['type'] = 'inline'); + + //hide navigation when mobile + drupal_add_css('#block-system-navigation {display:none;}', $option['type'] = 'inline'); + + //hide level blocks when mobile + drupal_add_css('#block-qtici-qtici-exercises-a {display:none;}', $option['type'] = 'inline'); + drupal_add_css('#block-qtici-qtici-exercises-b {display:none;}', $option['type'] = 'inline'); + drupal_add_css('#block-qtici-qtici-exercises-c {display:none;}', $option['type'] = 'inline'); + + //hide administration when mobile + drupal_add_css('#block-menu-menu-course-administration {display:none;}', $option['type'] = 'inline'); + + //hide search when mobile + drupal_add_css('#block-search-form {display:none;}', $option['type'] = 'inline'); + } + + //get the form from the function and give it to the template + $form = drupal_get_form("qtici_item_entity_form", $entity); + $templateObj["form"] = drupal_render($form); + $templateObj["itemid"] = $entity->id; + + if(isset($_SESSION['qtici']['visibility_' . $entity->id])){ + unset($_SESSION['qtici']['visibility_' . $entity->id]); + } + + //look if there already a session exists, otherwise make a new one and give the value 1 + if (!isset($_SESSION['qtici']['visibility_' . $entity->id])) { + $_SESSION['qtici']['visibility_' . $entity->id] = 1; + } + + //calls the custom thme function for the entity + $entity->contentEntity += array( + '#theme' => 'qtici_item_entity', + '#element' => $templateObj, + '#view_mode' => $view_mode, + '#language' => LANGUAGE_NONE, + ); + + // Now to invoke some hooks. We need the language code for + // hook_entity_view(), so let's get that. + global $language; + $langcode = $language->language; + // And now invoke hook_entity_view(). + module_invoke_all('entity_view', $entity, $entity_type, $view_mode, $langcode); + // Now invoke hook_entity_view_alter(). + drupal_alter(array('qtici_itemType_entity_view', 'entity_view'), $entity->contentEntity, $entity_type); + // And finally return the content. + return $entity->contentEntity; +} + +/** + * Adds our theme specificiations to the Theme Registry. + */ +function qtici_theme($existing, $type, $theme, $path) { + return array( + 'qtici_item_entity' => array( + 'variables' => array('element' => null), + 'template' => '/my_theme/qtici_item_entity_template' + ), + 'qtici_test_entity' => array( + 'variables' => array('element' => null), + 'template' => '/my_theme/qtici_test_entity_template' + ), + 'qtici_test' => array( + 'render element' => 'elements', + 'template' => 'test', + ), + // Theme for radio group. + 'qtici_item_form_radios' => array( + 'render element' => 'elements', + 'arguments' => array('elements' => NULL), + ), + // Theme for single radio. + 'qtici_item_form_radio' => array( + 'render element' => 'elements', + 'arguments' => array('elements' => NULL), + ), + // Theme for checkbox group. + 'qtici_item_form_checkboxes' => array( + 'render element' => 'elements', + 'arguments' => array('elements' => NULL), + ), + // Theme for single checkbox. + 'qtici_item_form_checkbox' => array( + 'render element' => 'elements', + 'arguments' => array('elements' => NULL), + ), + 'contextual' => array( + 'template' => '/my_theme/qtici_contextual_entity_template', + 'variables' => array('links' => array(), 'destination' => null) + ), + // Theme for qtici statistics + 'qtici_statistics' => array( + 'variables' => array('header' => NULL, 'rows' => NULL, 'title' => NULL), + 'file' => 'views.inc', + ), + ); +} + +function qtici_test_entity_view($entity, $view_mode = 'tweaky') { + drupal_set_title($entity->title); + //initialize a session with the test id so you know from which test you are comming + $_SESSION['entity_test']['testid'] = $entity->id; + +// Our entity type, for convenience. + $entity_type = 'qtici_test'; + // Start setting up the content. + $entity->content = array( + '#view_mode' => $view_mode, + ); + // Build fields content - this is where the Field API really comes in to play. + // The task has very little code here because it all gets taken care of by + // field module. + // field_attach_prepare_view() lets the fields load any data they need + // before viewing. + field_attach_prepare_view($entity_type, array($entity->id => $entity), $view_mode); + // We call entity_prepare_view() so it can invoke hook_entity_prepare_view() + // for us. + entity_prepare_view($entity_type, array($entity->id => $entity)); + // Now field_attach_view() generates the content for the fields. + $entity->content += field_attach_view($entity_type, $entity, $view_mode); + + // OK, Field API done, now we can set up some of our own data. + + $form = drupal_get_form("qtici_test_entity_form", $entity); + $templateObj["form"] = drupal_render($form); + + //lay-out configuration for mobile devices + if (module_exists('mobile_theme')) { + + //hide left sidebar when mobile + drupal_add_css('#sidebar-first {display:none;}', $option['type'] = 'inline'); + + //hide navigation when mobile + drupal_add_css('#block-system-navigation {display:none;}', $option['type'] = 'inline'); + + //hide level blocks when mobile + drupal_add_css('#block-qtici-qtici-exercises-a {display:none;}', $option['type'] = 'inline'); + drupal_add_css('#block-qtici-qtici-exercises-b {display:none;}', $option['type'] = 'inline'); + drupal_add_css('#block-qtici-qtici-exercises-c {display:none;}', $option['type'] = 'inline'); + + //hide administration when mobile + drupal_add_css('#block-menu-menu-course-administration {display:none;}', $option['type'] = 'inline'); + + //hide search when mobile + drupal_add_css('#block-search-form {display:none;}', $option['type'] = 'inline'); + } + + //initilize the time + $templateObj["time"] = ''; + + //look if there is a time limmit on the test when yes put the appropriate js for it in the test + if (!empty($entity->duration) && $entity->duration != 0) { + //count the amout of mili seconds + $time = ($entity->duration * 60) * 1000; + //submit the page after the amout of mili seconds + drupal_add_js('function f() {document.getElementById("edit-submit").click(); }setTimeout(f,' . $time . ')', 'inline'); + + //give the minutes for the test to the JavaScript file + drupal_add_js(array('qtici' => array('time' => $entity->duration)), 'setting'); + //call the timer JavaScript file + drupal_add_js(drupal_get_path('module', 'qtici') . '/js/timer.js'); + + //initialize a text and give it to the template + $templateObj["time"] = 'Voor deze test krijgt u ' . $entity->duration . ' minuten tijd. Resterende tijd: '; + } + + //show a link to the statistic page of the test when the logged in user is a teacher + $templateObj["statisticLink"] = ""; + if (user_access('teacher')) { + $templateObj["statisticLink"] = l("Statestieken van de test", "qtici/statistics/" . $entity->id); + } + + $entity->content += array( + '#theme' => 'qtici_test_entity', + '#element' => $templateObj, + '#view_mode' => $view_mode, + '#language' => LANGUAGE_NONE, + ); + + return $entity->content; +} + +function qtici_test_entity_overview_items($entity) { + + //get all the sections from the test + $sections = $entity->getAllSectionsByTest(); + + //get all the items from all sections + $entities = array(); + foreach ($sections as $section) { + $entities = array_merge($entities, $section->getItemsBySection()); + } + + if (!empty($entities)) { + foreach ($entities as $entity) { + // Create tabular rows for our entities. + $rows[] = array( + 'data' => array( + 'title' => l($entity->title, 'qtici/item/' . $entity->id), + ), + ); + } + // Put our entities into a themed table. See theme_table() for details. + $content['entity_table'] = array( + '#theme' => 'table', + '#rows' => $rows, + '#header' => array(t('Oefeningen'),), + ); + } + else { + // There were no entities. Tell the user. + $content[] = array( + '#type' => 'item', + '#markup' => t('No test entities currently exist.'), + ); + } + return $content; +} + +function qtici_item_form_submit($form, &$form_state) { + $entity = $form_state['values']['item']; + $entity->title = $form_state['values']['title']; + $entity->sectionid = $form_state['values']['sectionid']; + $entity->description = serialize($form_state['values']['description']); + $entity->type = $form_state['values']['type']; + $entity->quotation = $form_state['values']['quotation']; + $entity->question = $form_state['values']['question']; + $entity->max_attempts = $form_state['values']['max_attempts']; + $entity->ordering = $form_state['values']['ordering']; + $entity->score = $form_state['values']['score']; + + field_attach_submit('qtici_item', $entity, $form, $form_state); + //attention only SCQ exercise can at the moment be converted to MRK exercises + $entity = qtici_item_entity_save($entity); + + //get all the possibilitys by that item + $possibilities = _qtici_loadPossibilitiesByItemID($form_state['values']['id']); + + //run through all the possibility textboxes + foreach ($possibilities as $possibility) { + + $possibility->possibility = $form_state['values']['possibility' . $possibility->id]; + qtici_possibility_entity_save($possibility); + } + + drupal_set_message("De oefening is succesvol geüpdate!"); +} + +function qtici_test_form_submit($form, &$form_state) { + $entity = $form_state['values']['test']; + $entity->title = $form_state['values']['title']; + $entity->description = serialize($form_state['values']['description']); + $entity->duration = $form_state['values']['duration']; + $entity->passing_score = $form_state['values']['passing_score']; + $entity->published = $form_state['values']['published']; + $entity->answers_in = $form_state['values']['answers_in']; + $entity->answers_out = $form_state['values']['answers_out']; + $entity->show_answer = $form_state['values']['show_answer']; + $entity->check_answer = $form_state['values']['check_answer']; + $entity->course = $form_state['values']['course']; + $entity->topic = $form_state['values']['topic']; + $entity->level = $form_state['values']['level']; + + field_attach_submit('qtici_test', $entity, $form, $form_state); + $entity = qtici_test_entity_save($entity); + $form_state['redirect'] = 'qtici/test/' . $form_state['values']['id']; +} + +function qtici_item_entity_save(&$entity) { + return entity_get_controller('qtici_item')->save($entity); +} + +function qtici_possibility_entity_save(&$entity) { + return entity_get_controller('qtici_possibility')->save($entity); +} + +function qtici_test_entity_save(&$entity) { + return entity_get_controller('qtici_test')->save($entity); +} + +function qtici_item_entity_uri($basic) { + return array( + 'path' => 'qtici/item/' . $basic->id, + ); +} + +function qtici_test_entity_uri($basic) { + return array( + 'path' => 'qtici/test/' . $basic->id, + ); +} + +/** + * Implements hook_library(); + */ +function qtici_library() { + // Library One. + $libraries['images'] = array( + 'title' => 'images', + 'version' => '1', + 'css' => array( + drupal_get_path('module', 'qtici') . '/css/images.css' => array(), + ), + ); + + //library 3 + $libraries['exercise'] = array( + 'title' => 'exercise', + 'version' => '1', + 'js' => array( + drupal_get_path('module', 'qtici') . '/js/exercise.js' => array(), + ), + ); + + $libraries['mediaelement'] = array( + 'title' => 'mediaelement', + 'version' => '2.12.0', + 'js' => array( + drupal_get_path('module', 'qtici') . '/js/mediaelement/build/mediaelement-and-player.min.js' => array(), + ), + ); + + $libraries['recorder'] = array( + 'title' => 'recorder', + 'version' => '1.0', + 'js' => array( + drupal_get_path('module', 'qtici') . '/js/recorder/recorder.js' => array(), + ), + ); + + $libraries['recorder-conf'] = array( + 'title' => 'recorder-conf', + 'version' => '1.0', + 'js' => array( + drupal_get_path('module', 'qtici') . '/js/recorder-conf.js' => array(), + ), + ); + + $libraries['mediaelement-css'] = array( + 'title' => 'mediaelement CSS', + 'version' => '2.12.0', + 'css' => array( + drupal_get_path('module', 'qtici') . '/js/mediaelement/build/mediaelementplayer.css' => array(), + ), + ); + + //library 14 + $libraries['timeSpent'] = array( + 'title' => 'timeSpent', + 'version' => '1', + 'js' => array( + drupal_get_path('module', 'qtici') . '/js/timeSpent.js' => array(), + ), + ); + //library 15 + $libraries['videoQuestion'] = array( + 'title' => 'videoQuestion', + 'version' => '1', + 'js' => array( + drupal_get_path('module', 'qtici') . '/js/videoQuestion.js' => array(), + ), + ); + $libraries['DADQuestion'] = array( + 'title' => 'DADQuestion', + 'version' => '1', + 'js' => array( + drupal_get_path('module', 'qtici') . '/js/DADQuestion.js' => array(), + ), + ); + $libraries['markerQuestion'] = array( + 'title' => 'markerQuestion', + 'version' => '1', + 'js' => array( + drupal_get_path('module', 'qtici') . '/js/markerQuestion.js' => array(), + ), + ); + //library 16 + $libraries['filter'] = array( + 'title' => 'filter', + 'version' => '1', + 'js' => array( + drupal_get_path('module', 'qtici') . '/js/filter.js' => array(), + ), + ); + + //library 19 + $libraries['css'] = array( + 'title' => 'css', + 'version' => '1', + 'js' => array( + drupal_get_path('module', 'qtici') . '/css/css.css' => array(), + ), + ); + + return $libraries; +} + +/** + * Implements hook_permission(). + * + * Since the access to our new custom pages will be granted based on + * special permissions, we need to define what those permissions are here. + * This ensures that they are available to enable on the user role + * administration pages. + */ +function qtici_permission() { + $permissions = array( + 'teacher' => array( + 'title' => t('Administer Upload Courses OLAT module'), + 'description' => t('Perform administration tasks for the Upload OLAT Courses module.'), + 'restrict access' => TRUE, + ), + 'student' => array( + 'title' => t('See Upload Courses OLAT module'), + 'description' => t('Perform student tasks for the Upload OLAT Courses module.'), + 'restrict access' => TRUE, + ), + ); + + return $permissions; +} + +/** + * Implements hook_menu(). + * + * Here we set up the URLs (menu entries) for the + * form examples. Note that most of the menu items + * have page callbacks and page arguments set, with + * page arguments set to be functions in external files. + */ +function qtici_menu() { + qtici_create_custom_menu(); + + $items['oefeningen/publish/%'] = array( + 'title' => '(Un)Publish test', + 'page callback' => '_qtici_publishTest', + 'page arguments' => array(2), + 'access callback' => 'access_teacher', + 'access arguments' => array(1), + 'type' => MENU_CALLBACK, + ); + + $items['oefeningen/upload'] = array( + 'title' => 'Uploaden', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_zip'), + 'access callback' => 'access_teacher', + 'access arguments' => array(1), + 'description' => 'Upload OLAT Course with QTI Tests', + 'menu_name' => 'menu-course-administration', + 'type' => MENU_NORMAL_ITEM, + 'weight' => 4, + ); + + $items['oefeningen/overview/autocomplete'] = array( + 'page callback' => '_qtici_autocomplete', + 'access arguments' => array('any'), + 'type' => MENU_CALLBACK + ); + + $items['qtici/statistics'] = array( + 'title' => 'Statistieken', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_pageStatistics_form'), + 'access arguments' => array('teacher'), + 'description' => 'Overview of all QTI Tests', + 'menu_name' => 'menu-course-administration', + 'type' => MENU_NORMAL_ITEM, + 'weight' => 3, + ); + + $items['qtici/statistics/%qtici_test_entity'] = array( + 'title callback' => 'qtici_statistics_title', + 'title arguments' => array(2), + 'page callback' => '_qtici_testStatistics', + 'page arguments' => array(2), + 'access arguments' => array('teacher'), + 'type' => MENU_CALLBACK, + ); + + $items['oefeningen/overview/process_page'] = array( + 'page callback' => 'process_overview_test', + 'access arguments' => array('teacher'), + 'type' => MENU_CALLBACK, + ); + + $items['qtici/item/add'] = array( + 'title' => 'Uploaden', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_zip'), + 'access arguments' => array('teacher'), + ); + + $items['qtici/item/%qtici_item_entity'] = array( + 'title callback' => 'qtici_item_entity_title', + 'title arguments' => array(2), + 'page callback' => 'qtici_item_entity_view', + 'page arguments' => array(2), + 'access callback' => 'access_teacher_student', + 'access arguments' => array('teacher'), + ); + + $items['qtici/item/%qtici_item_entity/view'] = array( + 'title' => 'Overzicht', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'access arguments' => array('teacher'), + ); + + $items['qtici/item/%qtici_item_entity/edit'] = array( + 'title' => 'Bewerken', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_item_entity_edit', 2), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('teacher'), + ); + + $items['admin/test/manage'] = array( + 'title' => 'Manage tests', + 'page callback' => 'qtici_test_entity_admin_page', + 'access arguments' => array('teacher'), + ); + + $items['qtici/test/%qtici_test_entity'] = array( + 'title callback' => 'qtici_test_entity_title', + 'title arguments' => array(2), + 'page callback' => 'qtici_test_entity_view', + 'page arguments' => array(2), + 'access callback' => 'access_teacher_student', + 'access arguments' => array('student'), + ); + + $items['qtici/test/%qtici_test_entity/view'] = array( + 'title' => 'Overzicht', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'access arguments' => array('student'), + ); + + $items['qtici/test/%qtici_test_entity/edit'] = array( + 'title' => 'Bewerken', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_test_entity_edit_form', 2), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('teacher'), + ); + + $items['qtici/test/%qtici_test_entity/items'] = array( + 'title' => 'Oefeningen', + 'page callback' => 'qtici_test_entity_overview_items', + 'page arguments' => array(2), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('teacher'), + ); + + // Admin menus + + $items['admin/item/%qtici_item_entity/edit'] = array( + 'title' => 'Bewerken', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_item_entity_edit', 2), + 'access callback' => 'access_teacher', + 'access arguments' => array('teacher'), + ); + + $items['admin/content/tests/addTest'] = array( + 'title' => 'Add simple test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_zip'), + 'access arguments' => array('teacher'), + ); + + $items['admin/content/tests/addCategories'] = array( + 'title' => 'Categories', + 'page callback' => '_qtici_get_tag_cloud', + 'access arguments' => array('teacher'), + ); + + $items['admin/structure/tests'] = array( + 'title' => 'Tests', + 'description' => 'Manage test Entities Structure', + 'access arguments' => array('teacher'), + ); + + // This provides a place for Field API to hang its own + // interface and has to be the same as what was defined + // in basic_entity_info() above. + $items['admin/structure/tests/qtici/manage'] = array( + 'title' => 'Simple test', + 'description' => 'Manage simple test entities', + 'page callback' => 'qtici_simpleTest_admin_page', + 'access arguments' => array('teacher'), + ); + + // Qtici taxonomy menu + + $items['qtici/taxonomy'] = array( + 'title' => 'Categories', + 'page callback' => '_qtici_taxonomy_listByTag', + 'access arguments' => array('student'), + 'file' => 'qtici.taxonomy.inc', + 'type' => MENU_NORMAL_ITEM, + ); + + $items['qtici/taxonomy/%'] = array( + 'title' => 'Category', + 'title callback' => '_qtici_taxonomy_title', + 'page callback' => '_qtici_taxonomy_listByTag', + 'page arguments' => array(2), + 'access arguments' => array('student'), + 'file' => 'qtici.taxonomy.inc', + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +function qtici_simpleTest_admin_page() { + + $output = '<p>' . t('Manage simple tests from !here', array('!here' => l(t('here'), 'admin/test/manage'))) . '</p>'; + + return $output; +} + +function access_teacher_student() { + if (user_access('teacher')) { + return true; + } + if (user_access('student')) { + return true; + } +} + +function access_teacher_student_anon() { + if (user_access('teacher')) { + return true; + } + if (user_access('student')) { + return true; + } + if (user_access('anonymous')) { + return true; + } +} + +function access_student_anon() { + if (user_access('student')) { + return true; + } + if (user_access('anonymous')) { + return true; + } +} + +function access_teacher() { + if (user_access('teacher')) { + return true; + } +} + +function access_student() { + if (user_access('student')) { + return true; + } +} + +function qtici_create_custom_menu() { + + /* Modified by Elisa: if they do not exist!! */ + $result = db_query("SELECT * FROM {menu_custom} WHERE menu_name = 'menu-course-administration' OR menu_name = 'menu-course-student'"); + $num = $result->rowCount(); + + if ($num == 0) { + $menu = array(); + $menu['menu_name'] = 'menu-course-administration'; + $menu['title'] = 'Administratie'; + $menu['description'] = 'This menu contains links for OLAT Course Administration.'; + menu_save($menu); + + $menu_block = array( + 'module' => 'menu', + 'delta' => 'menu-course-administration', + 'theme' => 'bartik', // Either get the active theme or you can do it for all themes + 'region' => 'sidebar_first', // Where you want to place it, theme dependant sidebar_first + 'visibility' => 0, + 'status' => 1, + 'pages' => "", + ); + + drupal_write_record('block', $menu_block); + + $menu = array(); + $menu['menu_name'] = 'menu-student'; + $menu['title'] = 'Student'; + $menu['description'] = 'This menu contains links for making OLAT Courses and Tests'; + menu_save($menu); + + $menu_block = array( + 'module' => 'menu', + 'delta' => 'menu-student', + 'theme' => 'bartik', // Either get the active theme or you can do it for all themes + 'region' => 'sidebar_first', // Where you want to place it, theme dependant sidebar_first + 'visibility' => 0, + 'status' => 1, + 'pages' => "", + ); + + drupal_write_record('block', $menu_block); + } + else { + drupal_set_message(t('Costum menus and blocks could not be created due to inconsistencies with the existing ones.')); + } +} + +/** + * Implements hook_block_info(). + * + * This hook declares what blocks are provided by the module. + */ +function qtici_block_info() { + + $blocks['qtici_exercises_A'] = array( + 'info' => t('Exercises menu - Level A1/2'), + 'region' => 'sidebar_first', + 'weight' => 1, + 'status' => 1, + 'cache' => 'DRUPAL_NO_CACHE', + ); + + $blocks['qtici_exercises_B'] = array( + 'info' => t('Exercises menu - Level B1/2'), + 'region' => 'sidebar_first', + 'weight' => 1, + 'status' => 1, + 'cache' => 'DRUPAL_NO_CACHE', + ); + + $blocks['qtici_exercises_C'] = array( + 'info' => t('Exercises menu - Level C1/2'), + 'region' => 'sidebar_first', + 'weight' => 1, + 'status' => 1, + 'cache' => 'DRUPAL_NO_CACHE', + ); + + return $blocks; +} + +/** + * Implements hook_block_view(). + * + * This hook generates the contents of the blocks themselves. + */ + +/** + * This function will show you the dynamic treeview in sidebar first and will trigger the recursive function "recursive" for adding the list items + * into the 'content' of the block + */ +function qtici_block_view($delta = '') { + $block = array(); + + switch ($delta) { + case 'qtici_exercises_A': + + $block = _qtici_generate_block('A'); + break; + + case 'qtici_exercises_B': + + $block = _qtici_generate_block('B'); + break; + + case 'qtici_exercises_C': + + $block = _qtici_generate_block('C'); + break; + } + return $block; +} + +function _qtici_menu_callback($topic, $level, $testId) { + + drupal_add_js(array('qtici' => array('topic' => $topic, 'testId' => $testId)), 'setting'); + + $test = qtici_test_entity_load($testId); + + $form = drupal_get_form('qtici_test_entity_form', $test); + + $html = drupal_render($form); + + return $html; +} + +function _qtici_generate_block($str) { + + drupal_add_css(drupal_get_path('module', 'qtici') . '/css/block_exercises.css'); + drupal_add_js(drupal_get_path('module', 'qtici') . '/js/left_menu.js', 'file'); + + $block['subject'] = t('Oefeningen'); + $block['content'] = ''; + + global $base_url; + + global $_qtici_topics; + foreach ($_qtici_topics as $key => $topic) { + $exercises = _qtici_get_exercisesLT($str, $topic); + $block['content'] .= '<ul class="block_exercises topic_' . $key . '">'; + $block['content'] .= '<li><a href="#" class="qtici-' . $key . '">' . $topic . '</a>'; + if (!empty($exercises)) { + $block['content'] .= '<ul>'; + + foreach ($exercises as $exercise) { + $block['content'] .= '<li><a href="' . $base_url . '/qtici/test/' . $exercise['id'] . '" class="qtici-' . $exercise['level'] . '">' . t($exercise['title']) . '</a></li>'; + } + + //$block['content'] .= '</li>'; + $block['content'] .= '</ul>'; + } + $block['content'] .= '</li>'; + $block['content'] .= '</ul>'; + } + + return $block; +} + +function qtici_test_adminaccess($op, $account = NULL) { + return user_access('teacher', $account); +} diff --git a/qtici.taxonomy.inc b/qtici.taxonomy.inc new file mode 100644 index 0000000000000000000000000000000000000000..c4a4df3fc09ea96258b5c7e8986b5f6d36f58563 --- /dev/null +++ b/qtici.taxonomy.inc @@ -0,0 +1,123 @@ +<?php +/** + * @file - All functions to get taxonomy working with this module come here + */ + +/** + * Returns a list of entities currently asociated to a tag + */ +function _qtici_taxonomy_listByTag($tid = NULL) { + + $list = ''; + + if (empty($tid)) { + $list = _qtici_get_tag_cloud(); + } + else { + $query = db_select('taxonomy_entity_index', 'i'); + $query->fields('i', array( + 'entity_id', + )); + $query->condition('i.bundle', 'qtici_test', '='); + $query->condition('i.tid', $tid, '='); + $ids = $query->execute()->fetchCol(); + + $tests = qtici_test_entity_load_multiple($ids); + $items = array(); + + foreach ($tests as $test) { + $items[] = l($test->title, 'qtici/test/' . $test->id); + } + $term = taxonomy_term_load($tid); + + $list .= '<h2>' . $term->name . '</h2>'; + $list .= theme('item_list', array('items' => $items, 'type' => 'ul', 'attributes' => array('class' => 'taxonomy_list'))); + + $list .= '<p>' . l('All categories', 'qtici/taxonomy') . '</p>'; + } + + return $list; +} + +/** + * Returns a plain list with the tags associated with an entity + */ +function _qtici_get_tag_list($entity) { + + $rray = array(); + $tags = _qtici_tag_query($entity->id); + + foreach ($tags as $tag) { + $rray[] = l($tag->name, 'qtici/taxonomy/' . $tag->tid); + } + + if (empty($rray)) { + $text = t('There are no categories defined for this test'); + } + else { + $text = implode(' ', $rray); + } + + $list = '<h3>' . t('Categories:') . ' ' . $text . '</h3>'; + + return $list; +} + +/** + * Tag query: returns an object with the tags of an entity, or all available tags + */ +function _qtici_tag_query($id = NULL) { + + $query = db_select('taxonomy_term_data', 'd'); + $query->leftJoin('taxonomy_entity_index', 'i', 'i.tid = d.tid'); + $query->fields('d', array( + 'tid', + 'name', + )); + $query->condition('i.bundle', 'qtici_test', '='); + if (isset($id)) { + $query->condition('i.entity_id', $id, '='); + } + $query->addExpression('COUNT(i.tid)', 'count_tid'); + $query->orderBy('count_tid', 'DESC'); + $query->groupBy('tid'); + $tags = $query->execute(); + + return $tags; +} + +/** + * Tag Cloud: returns a paragraph of tags styled by weigth + */ +function _qtici_get_tag_cloud() { + $tags = _qtici_tag_query(); + + $rray = array(); + + $max = NULL; + $min = NULL; + $i = 1; + $terms = array(); + // First loop to calculate max and min number of reps + foreach ($tags as $tag) { + if ($i == 1) { + $max = $tag->count_tid; + } + $min = $tag->count_tid; + $i++; + $terms[] = $tag; + } + + foreach ($terms as $tag) { + $size = 10 + (($max - ($max - ($tag->count_tid - $min)))*62/ ($max - $min)); + $rray[] = l($tag->name, 'qtici/taxonomy/' . $tag->tid, array('attributes' => array('style' => 'font-size:' . $size . 'px;',))); + } + shuffle($rray); + $list = '<p>' . implode(' ', $rray) . '</p>'; + + if (user_access('teacher')) { + $list .= '<p>' . t('Manage categories from !here', array('!here' => l(t('here'), 'admin/structure/taxonomy/tags'))) . '</p>'; + } + + return $list; +} diff --git a/qtici.views.inc b/qtici.views.inc new file mode 100755 index 0000000000000000000000000000000000000000..98e05abbdbac5f2cdeee8155405a7d96e3c7bb69 --- /dev/null +++ b/qtici.views.inc @@ -0,0 +1,1336 @@ +<?php + +function qtici_views_data() { + + $data['qtici_test']['table']['group'] = t('Test table'); + + $data['qtici_test']['table']['base'] = array( + 'field' => 'id', // This is the identifier field for the view. + 'title' => t('Test table'), + 'help' => t('Test table that conatians the tests'), + 'weight' => -10, + ); + + $data['qtici_test']['id'] = array( + 'title' => t('ID'), + 'help' => t('The primary identifier for a test.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + ); + + $data['qtici_test']['olat_testid'] = array( + 'title' => t('Olat_testid'), + 'help' => t('Contains the id given by OLAT, the course will only this ID (unique)'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['qtici_test']['title'] = array( + 'title' => t('Title'), + 'help' => t('Contains the title of the test.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + $data['qtici_test']['description'] = array( + 'title' => t('Description'), + 'help' => t('Contains the description or objectives of the test.'), + 'field' => array( + 'handler' => 'views_handler_field_description', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + ); + + $data['qtici_test']['duration'] = array( + 'title' => t('Duration'), + 'help' => t('Contains the duration of how long the test takes.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + $data['qtici_test']['passing_score'] = array( + 'title' => t('Passing_score'), + 'help' => t('Contains the score to pass the test.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['qtici_test']['published'] = array( + 'title' => t('Published'), + 'help' => t('Contains TRUE = 1 if the test is published and FALSE = 0 if not.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['qtici_test']['answers_in'] = array( + 'title' => t('Answers_in'), + 'help' => t('Contains TRUE = 1 if the answers (and feedback) are displayed in the test and FALSE = 0 if not.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['qtici_test']['answers_out'] = array( + 'title' => t('Answers_out'), + 'help' => t('Contains TRUE = 1 if the answers (and feedback) are displayed after processing the form and FALSE = 0 if not.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['qtici_test']['show_answer'] = array( + 'title' => t('Show_answer'), + 'help' => t('Contains TRUE = 1 if the button show answer is displayed in the test and FALSE = 0 if not.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['qtici_test']['check_answer'] = array( + 'title' => t('Check_answer'), + 'help' => t('Contains TRUE = 1 if the button check answer is displayed after processing the form and FALSE = 0 if not.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['qtici_test']['date'] = array( + 'title' => t('Date'), + 'help' => t('Contains the date when a test is uploaded.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['qtici_test']['course'] = array( + 'title' => t('Course'), + 'help' => t('Contains the course to which a test belongs.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + $data['qtici_test']['topic'] = array( + 'title' => t('Topic'), + 'help' => t('Keyword for ordering the tests by topic'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + $data['qtici_test']['level'] = array( + 'title' => t('Level'), + 'help' => t('Keyword for ordering the tests by level (A, B or C)'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + $data['qtici_section']['table']['group'] = t('Section table'); + + $data['qtici_section']['table']['base'] = array( + 'field' => 'id', // This is the identifier field for the view. + 'title' => t('Section table'), + 'help' => t('Section table that conatians the sections of the test'), + 'weight' => -10, + ); + + $data['qtici_section']['id'] = array( + 'title' => t('ID'), + 'help' => t('The primary identifier for a section.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + ); + + // Example numeric text field. + $data['qtici_section']['testid'] = array( + 'title' => t('Testid'), + 'help' => t('FK to qti_test.id.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'label' => t('Test ID'), + 'base' => 'qtici_test', + 'base field' => 'id', + 'skip base' => array(), + ), + ); + + // Example plain text field. + $data['qtici_section']['title'] = array( + 'title' => t('Title'), + 'help' => t('Contains the title of the section.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Example plain text field. + $data['qtici_section']['description'] = array( + 'title' => t('Description'), + 'help' => t('Contains the description or objectives of the section.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Example plain text field. + $data['qtici_section']['ordering'] = array( + 'title' => t('Ordering'), + 'help' => t('Contains whether items in the section should be ordered sequential or random.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + $data['qtici_item']['table']['group'] = t('Item table'); + + $data['qtici_item']['table']['base'] = array( + 'field' => 'id', // This is the identifier field for the view. + 'title' => t('Item table'), + 'help' => t('Item table that conatians the exercises'), + 'weight' => -10, + ); + + $data['qtici_item']['id'] = array( + 'title' => t('ID'), + 'help' => t('The primary identifier for an item.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + ); + + // Example numeric text field. + $data['qtici_item']['sectionid'] = array( + 'title' => t('Sectionid'), + 'help' => t('FK to qti_section.id.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'label' => t('section ID'), + 'base' => 'qtici_section', + 'base field' => 'id', + 'skip base' => array(), + ), + ); + + // Example plain text field. + $data['qtici_item']['title'] = array( + 'title' => t('Title'), + 'help' => t('Contains the title of the item.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Example plain text field. + $data['qtici_item']['description'] = array( + 'title' => t('Description'), + 'help' => t('Contains the description or objectives of the item.'), + 'field' => array( + 'handler' => 'views_handler_field_serialized', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Example plain text field. + $data['qtici_item']['type'] = array( + 'title' => t('Type'), + 'help' => t('Contains the type of question: SCQ, MCQ, FIB, KPRIM.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Example plain text field. + $data['qtici_item']['quotation'] = array( + 'title' => t('Quotation'), + 'help' => t('Contains how the quotation should happen, score per answer or all correct.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Example plain text field. + $data['qtici_item']['question'] = array( + 'title' => t('Question'), + 'help' => t('Contains the question.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + + // Example numeric text field. + $data['qtici_item']['max_attempts'] = array( + 'title' => t('Max_attempts'), + 'help' => t('Contains how many times the item can be filled in.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example numeric text field. + $data['qtici_item']['ordering'] = array( + 'title' => t('Ordering'), + 'help' => t('Contains TRUE = 1 if items should be ordered random and FALSE = 0 if not.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example numeric text field. + $data['qtici_item']['score'] = array( + 'title' => t('Score'), + 'help' => t('Contains the score if quotation = all correct.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example plain text field. + $data['qtici_item']['content'] = array( + 'title' => t('Content'), + 'help' => t('Contains the content of the item: text + format'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + $data['qtici_possibility']['table']['group'] = t('Possibiliy table'); + + $data['qtici_possibility']['table']['base'] = array( + 'field' => 'id', // This is the identifier field for the view. + 'title' => t('Possibiliy table'), + 'help' => t('Possibiliy table that conatians the exercises'), + 'weight' => -10, + ); + + $data['qtici_possibility']['id'] = array( + 'title' => t('ID'), + 'help' => t('The primary identifier for a possibility.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + ); + + // Example numeric text field. + $data['qtici_possibility']['itemid'] = array( + 'title' => t('Itemid'), + 'help' => t('FK to qti_item.id.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'label' => t('Item ID'), + 'base' => 'qtici_item', + 'base field' => 'id', + 'skip base' => array(), + ), + ); + + // Example plain text field. + $data['qtici_possibility']['possibility'] = array( + 'title' => t('Possibility'), + 'help' => t('Contains a possibility: radio, textbox, checkbox, etc'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Example numeric text field. + $data['qtici_possibility']['is_correct'] = array( + 'title' => t('Is_correct'), + 'help' => t('Contains TRUE = 1 if possibility is correct or FALSE = 0 if not. This will only be used if item.type = SCQ, MCQ or KPRIM.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example plain text field. + $data['qtici_possibility']['answer'] = array( + 'title' => t('Answer'), + 'help' => t('Contains the text of the possibility'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Example numeric text field. + $data['qtici_possibility']['ordering'] = array( + 'title' => t('Ordering'), + 'help' => t('Contains the order of which the FIB should be displayed.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example numeric text field. + $data['qtici_possibility']['score'] = array( + 'title' => t('Score'), + 'help' => t('Contains the score if item.quotation = score per answer.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + + $data['qtici_feedback']['table']['group'] = t('Feedback table'); + + $data['qtici_feedback']['table']['base'] = array( + 'field' => 'id', // This is the identifier field for the view. + 'title' => t('Feedback table'), + 'help' => t('Feedback table that conatians the exercises'), + 'weight' => -10, + ); + + + $data['qtici_feedback']['id'] = array( + 'title' => t('ID'), + 'help' => t('The primary identifier for a feedback.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + ); + + // Example numeric text field. + $data['qtici_feedback']['itemid'] = array( + 'title' => t('Itemid'), + 'help' => t('FK to qti_item.id.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'label' => t('Item ID'), + 'base' => 'qtici_item', + 'base field' => 'id', + 'skip base' => array(), + ), + ); + + // Example numeric text field. + $data['qtici_feedback']['possibilityid'] = array( + 'title' => t('Possibilityid'), + 'help' => t('FK to qti_possibility.id.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'label' => t('possibilityid ID'), + 'base' => 'qtici_possibility', + 'base field' => 'id', + 'skip base' => array(), + ), + ); + + // Example numeric text field. + $data['qtici_feedback']['feedback_possibility'] = array( + 'title' => t('Feedback_possibility'), + 'help' => t('Contains the feedback for a certain possibility'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example numeric text field. + $data['qtici_feedback']['feedback_positive'] = array( + 'title' => t('Feedback_positive'), + 'help' => t('Contains the possitive (MASTERY) feedback.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example numeric text field. + $data['qtici_feedback']['feedback_negative'] = array( + 'title' => t('Feedback_negative'), + 'help' => t('Contains the negative (FAIL) feedback.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example numeric text field. + $data['qtici_feedback']['hint'] = array( + 'title' => t('Hint'), + 'help' => t('Contains a hint.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example numeric text field. + $data['qtici_feedback']['solution_feedback'] = array( + 'title' => t('Solution_feedback'), + 'help' => t('Contains the solution as feedback.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['qtici_test_statistics']['table']['group'] = t('test statistics table'); + + $data['qtici_test_statistics']['table']['base'] = array( + 'field' => 'id', // This is the identifier field for the view. + 'title' => t('test statistics table'), + 'help' => t('test statistics table that conatians the exercises'), + 'weight' => -10, + ); + + + $data['qtici_test_statistics']['id'] = array( + 'title' => t('ID'), + 'help' => t('The primary identifier for a teststatistic.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + ); + + // Example numeric text field. + $data['qtici_test_statistics']['testid'] = array( + 'title' => t('Testid'), + 'help' => t('FK to qti_test.id.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'label' => t('Test ID'), + 'base' => 'qtici_test', + 'base field' => 'id', + 'skip base' => array(), + ), + ); + + // Example numeric text field. + $data['qtici_test_statistics']['completed'] = array( + 'title' => t('completed'), + 'help' => t('Contains if the test has been completed.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + // Example plain text field. + $data['qtici_possibility']['time_spended'] = array( + 'title' => t('Time_spended'), + 'help' => t('Contains the time spended to complete a test.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, // This is use by the table display plugin. + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Example numeric text field. + $data['qtici_test_statistics']['score'] = array( + 'title' => t('Score'), + 'help' => t('Contains the score of a test.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + + // Example numeric text field. + $data['qtici_test_statistics']['date_started'] = array( + 'title' => t('Date_started'), + 'help' => t('Contains the date when a test is started.'), + 'field' => array( + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + return $data; +} + +/** + * Implements hook_views_default_views(). + */ +function qtici_views_default_views() { + $views = array(); + + //get the view + $testView = generate_test_vieuw(); + $itemView = generate_item_vieuw(); + $testStatisticView = generate_statistic_vieuw(); + + //set the view in the array of views + $views[$testView->name] = $testView; + $views[$itemView->name] = $itemView; + $views[$testStatisticView->name] = $testStatisticView; + + return $views; +} + +function generate_test_vieuw() { + $view = new view; + $view->name = 'tests'; + $view->description = 'A list of all tests'; + $view->tag = 'tests'; + $view->base_table = 'qtici_test'; + $view->human_name = 'Tests'; + $view->core = 7; + $view->api_version = '3.0-alpha1'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'Tests'; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['access']['perm'] = 'create any model type'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['pager']['options']['items_per_page'] = '10'; + $handler->display->display_options['style_plugin'] = 'table'; + $handler->display->display_options['style_options']['columns'] = array( + 'title' => 'title', + 'id' => 'id', + ); + $handler->display->display_options['style_options']['default'] = '-1'; + $handler->display->display_options['style_options']['info'] = array( + 'title' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + 'id' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + ); + $handler->display->display_options['style_options']['override'] = 1; + $handler->display->display_options['style_options']['sticky'] = 0; + $handler->display->display_options['style_options']['empty_table'] = 0; + /* No results behavior: Global: Text area */ + $handler->display->display_options['empty']['area']['id'] = 'area'; + $handler->display->display_options['empty']['area']['table'] = 'views'; + $handler->display->display_options['empty']['area']['field'] = 'area'; + $handler->display->display_options['empty']['area']['label'] = 'Leeg '; + $handler->display->display_options['empty']['area']['empty'] = FALSE; + $handler->display->display_options['empty']['area']['content'] = 'No tests have been created yet'; + + $fields = array("id", "title", "description", "duration", "passing_score", "published", + "answers_in", "answers_out", "show_answer", "check_answer", "date", "course", "topic", "level"); + + foreach ($fields as $fieldName) { + + /* Field: Model: ID */ + $handler->display->display_options['fields'][$fieldName]['id'] = $fieldName; + $handler->display->display_options['fields'][$fieldName]['table'] = 'qtici_test'; + $handler->display->display_options['fields'][$fieldName]['field'] = $fieldName; + $handler->display->display_options['fields'][$fieldName]['alter']['alter_text'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['make_link'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['absolute'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['external'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['replace_spaces'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['trim'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['nl2br'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['word_boundary'] = 1; + $handler->display->display_options['fields'][$fieldName]['alter']['ellipsis'] = 1; + $handler->display->display_options['fields'][$fieldName]['alter']['strip_tags'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['html'] = 0; + $handler->display->display_options['fields'][$fieldName]['element_label_colon'] = 1; + $handler->display->display_options['fields'][$fieldName]['element_default_classes'] = 1; + $handler->display->display_options['fields'][$fieldName]['hide_empty'] = 0; + $handler->display->display_options['fields'][$fieldName]['empty_zero'] = 0; + } + + /* Display: Page */ + $handler = $view->new_display('page', 'Page', 'models_admin_page'); + $handler->display->display_options['path'] = 'admin/content/tests/list'; + $handler->display->display_options['menu']['type'] = 'default tab'; + $handler->display->display_options['menu']['title'] = 'List'; + $handler->display->display_options['menu']['weight'] = '-10'; + $handler->display->display_options['tab_options']['type'] = 'tab'; + $handler->display->display_options['tab_options']['title'] = 'Tests'; + $handler->display->display_options['tab_options']['description'] = 'Manage tests'; + $handler->display->display_options['tab_options']['weight'] = '0'; + $handler->display->display_options['tab_options']['name'] = 'management'; + $translatables['tests'] = array( + t('Master'), + t('Models'), + t('more'), + t('Apply'), + t('Reset'), + t('Sort by'), + t('Asc'), + t('Desc'), + t('Items per page'), + t('- All -'), + t('Offset'), + t('Empty '), + t('No models have been created yet'), + t('Test ID'), + t('.'), + t(','), + t('Name'), + t('View'), + t('Operations links'), + t('Page'), + ); + + return $view; +} + +function generate_item_vieuw() { + $view = new view; + $view->name = 'Item'; + $view->description = 'A list of all Items'; + $view->tag = 'Items'; + $view->base_table = 'qtici_item'; + $view->human_name = 'Items'; + $view->core = 7; + $view->api_version = '3.0-alpha1'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'Items'; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['access']['perm'] = 'create any model type'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['pager']['options']['items_per_page'] = '10'; + $handler->display->display_options['style_plugin'] = 'table'; + $handler->display->display_options['style_options']['columns'] = array( + 'title' => 'title', + 'sectionid' => 'sectionid', + 'id' => 'id', + ); + $handler->display->display_options['style_options']['default'] = '-1'; + $handler->display->display_options['style_options']['info'] = array( + 'title' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + 'sectionid' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + 'id' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + ); + $handler->display->display_options['style_options']['override'] = 1; + $handler->display->display_options['style_options']['sticky'] = 0; + $handler->display->display_options['style_options']['empty_table'] = 0; + /* No results behavior: Global: Text area */ + $handler->display->display_options['empty']['area']['id'] = 'area'; + $handler->display->display_options['empty']['area']['table'] = 'views'; + $handler->display->display_options['empty']['area']['field'] = 'area'; + $handler->display->display_options['empty']['area']['label'] = 'Leeg '; + $handler->display->display_options['empty']['area']['empty'] = FALSE; + $handler->display->display_options['empty']['area']['content'] = 'No items have been created yet'; + + $fields = array("id", "sectionid", "title", "description", "type", "quotation", + "question", "max_attempts", "ordering", "score", "content"); + + foreach ($fields as $fieldName) { + + /* Field: Model: ID */ + $handler->display->display_options['fields'][$fieldName]['id'] = $fieldName; + $handler->display->display_options['fields'][$fieldName]['table'] = 'qtici_item'; + $handler->display->display_options['fields'][$fieldName]['field'] = $fieldName; + $handler->display->display_options['fields'][$fieldName]['alter']['alter_text'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['make_link'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['absolute'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['external'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['replace_spaces'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['trim'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['nl2br'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['word_boundary'] = 1; + $handler->display->display_options['fields'][$fieldName]['alter']['ellipsis'] = 1; + $handler->display->display_options['fields'][$fieldName]['alter']['strip_tags'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['html'] = 0; + $handler->display->display_options['fields'][$fieldName]['element_label_colon'] = 1; + $handler->display->display_options['fields'][$fieldName]['element_default_classes'] = 1; + $handler->display->display_options['fields'][$fieldName]['hide_empty'] = 0; + $handler->display->display_options['fields'][$fieldName]['empty_zero'] = 0; + } + + /* Display: Page */ + $handler = $view->new_display('page', 'Page', 'models_admin_page'); + $handler->display->display_options['path'] = 'admin/content/items/list'; + $handler->display->display_options['menu']['type'] = 'default tab'; + $handler->display->display_options['menu']['title'] = 'List'; + $handler->display->display_options['menu']['weight'] = '-10'; + $handler->display->display_options['tab_options']['type'] = 'tab'; + $handler->display->display_options['tab_options']['title'] = 'Items'; + $handler->display->display_options['tab_options']['description'] = 'Manage items'; + $handler->display->display_options['tab_options']['weight'] = '0'; + $handler->display->display_options['tab_options']['name'] = 'management'; + $translatables['items'] = array( + t('Master'), + t('Models'), + t('more'), + t('Apply'), + t('Reset'), + t('Sort by'), + t('Asc'), + t('Desc'), + t('Items per page'), + t('- All -'), + t('Offset'), + t('Empty '), + t('No models have been created yet'), + t('Test ID'), + t('.'), + t(','), + t('Name'), + t('View'), + t('Operations links'), + t('Page'), + ); + + return $view; +} + +function generate_statistic_vieuw() { + $view = new view; + $view->name = 'test statistics'; + $view->description = 'A list of all test statistics'; + $view->tag = 'test statistics'; + $view->base_table = 'qtici_test_statistics'; + $view->human_name = 'Test statistics'; + $view->core = 7; + $view->api_version = '3.0-alpha1'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'Test statistics'; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['access']['perm'] = 'create any model type'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['pager']['options']['items_per_page'] = '10'; + $handler->display->display_options['style_plugin'] = 'table'; + $handler->display->display_options['style_options']['columns'] = array( + 'date_started' => 'date_started', + 'testid' => 'testid', + 'id' => 'id', + ); + $handler->display->display_options['style_options']['default'] = '-1'; + $handler->display->display_options['style_options']['info'] = array( + 'date_started' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + 'testid' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + 'id' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + ); + $handler->display->display_options['style_options']['override'] = 1; + $handler->display->display_options['style_options']['sticky'] = 0; + $handler->display->display_options['style_options']['empty_table'] = 0; + /* No results behavior: Global: Text area */ + $handler->display->display_options['empty']['area']['id'] = 'area'; + $handler->display->display_options['empty']['area']['table'] = 'views'; + $handler->display->display_options['empty']['area']['field'] = 'area'; + $handler->display->display_options['empty']['area']['label'] = 'Leeg '; + $handler->display->display_options['empty']['area']['empty'] = FALSE; + $handler->display->display_options['empty']['area']['content'] = 'No test statistics have been created yet'; + + $fields = array("id", "testid", "completed", "time_spended", "score", "date_started"); + + foreach ($fields as $fieldName) { + + /* Field: Model: ID */ + $handler->display->display_options['fields'][$fieldName]['id'] = $fieldName; + $handler->display->display_options['fields'][$fieldName]['table'] = 'qtici_test_statistics'; + $handler->display->display_options['fields'][$fieldName]['field'] = $fieldName; + $handler->display->display_options['fields'][$fieldName]['alter']['alter_text'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['make_link'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['absolute'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['external'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['replace_spaces'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['trim'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['nl2br'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['word_boundary'] = 1; + $handler->display->display_options['fields'][$fieldName]['alter']['ellipsis'] = 1; + $handler->display->display_options['fields'][$fieldName]['alter']['strip_tags'] = 0; + $handler->display->display_options['fields'][$fieldName]['alter']['html'] = 0; + $handler->display->display_options['fields'][$fieldName]['element_label_colon'] = 1; + $handler->display->display_options['fields'][$fieldName]['element_default_classes'] = 1; + $handler->display->display_options['fields'][$fieldName]['hide_empty'] = 0; + $handler->display->display_options['fields'][$fieldName]['empty_zero'] = 0; + } + + /* Display: Page */ + $handler = $view->new_display('page', 'Page', 'models_admin_page'); + $handler->display->display_options['path'] = 'admin/content/test_statistics/list'; + $handler->display->display_options['menu']['type'] = 'default tab'; + $handler->display->display_options['menu']['title'] = 'List'; + $handler->display->display_options['menu']['weight'] = '-10'; + $handler->display->display_options['tab_options']['type'] = 'tab'; + $handler->display->display_options['tab_options']['title'] = 'Test statistics'; + $handler->display->display_options['tab_options']['description'] = 'Manage test statistics'; + $handler->display->display_options['tab_options']['weight'] = '0'; + $handler->display->display_options['tab_options']['name'] = 'management'; + $translatables['test_statistics'] = array( + t('Master'), + t('Models'), + t('more'), + t('Apply'), + t('Reset'), + t('Sort by'), + t('Asc'), + t('Desc'), + t('Items per page'), + t('- All -'), + t('Offset'), + t('Empty '), + t('No models have been created yet'), + t('Test ID'), + t('.'), + t(','), + t('Name'), + t('View'), + t('Operations links'), + t('Page'), + ); + + return $view; +} + +function qtici_views_plugins() { + $plugins = array(); + $plugins['argument validator'] = array( + 'taxonomy_term' => array( + 'title' => t('Taxonomy term'), + 'handler' => 'views_plugin_argument_validate_taxonomy_term', + // Declaring path explicitly not necessary for most modules. + 'path' => drupal_get_path('module', 'views') . '/modules/taxonomy', + ), + ); + + return array( + 'module' => 'views', // This just tells our themes are elsewhere. + 'argument validator' => array( + 'taxonomy_term' => array( + 'title' => t('Taxonomy term'), + 'handler' => 'views_plugin_argument_validate_taxonomy_term', + 'path' => drupal_get_path('module', 'views') . '/modules/taxonomy', // not necessary for most modules + ), + ), + 'argument default' => array( + 'taxonomy_tid' => array( + 'title' => t('Taxonomy term ID from URL'), + 'handler' => 'views_plugin_argument_default_taxonomy_tid', + 'path' => drupal_get_path('module', 'views') . '/modules/taxonomy', + 'parent' => 'fixed', + ), + ), + ); +} diff --git a/qtici_course/classes/Chapter.php b/qtici_course/classes/Chapter.php new file mode 100755 index 0000000000000000000000000000000000000000..b0c5c1f65dac1e4fe920e5b373c402c5239dfe8e --- /dev/null +++ b/qtici_course/classes/Chapter.php @@ -0,0 +1,173 @@ +<?php +class Chapter { + + private $id; + private $shortTitle; + private $type; + private $visibilityBeginDate; + private $visibilityEndDate; + private $accessBeginDate; + private $accessEndDate; + private $subjects = array(); + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct($id = 0, $shortTitle = "", $type = "", $visibilityBeginDate ="", $visibilityEndDate = "", $accessBeginDate ="", $accessEndDate ="") { + $this->id = $id; + $this->shortTitle = $shortTitle; + $this->type = $type; + $this->visibilityBeginDate = $visibilityBeginDate; + $this->visibilityEndDate = $visibilityEndDate; + $this->accessBeginDate = $accessBeginDate; + $this->accessEndDate = $accessEndDate; + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getId() { + return $this->id; + } + + public function getShortTitle() { + return $this->shortTitle; + } + + public function getType() { + return $this->type; + } + + public function getVisibilityBeginDate() { + return $this->visibilityBeginDate; + } + + public function getVisibilityEndDate() { + return $this->visibilityEndDate; + } + + public function getAccessBeginDate() { + return $this->accessBeginDate; + } + + public function getAccessEndDate() { + return $this->accessEndDate; + } + + public function getSubjects() { + return $this->subjects; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + + public function setId($id) { + $this->id = $id; + } + + public function setShortTitle($shortTitle) { + $this->shortTitle = $shortTitle; + } + + public function setType($type) { + $this->type = $type; + } + + function setVisibilityBeginDate($visibilityBeginDate) { + $this->visibilityBeginDate = $visibilityBeginDate; + } + + public function setVisibilityEndDate($visibilityEndDate) { + $this->visibilityEndDate = $visibilityEndDate; + } + + public function setAccessBeginDate($accessBeginDate) { + $this->accessBeginDate = $accessBeginDate; + } + + public function setAccessEndDate($accessEndDate) { + $this->accessEndDate = $accessEndDate; + } + + public function setSubject($subjects) { + array_push($this->subjects, $subjects); + } + +} + +?> + +<?php +// +//include 'ChapterDropFolder.php'; +//include 'ChapterPage.php'; +//include 'Subject.php'; +//class Chapter { +// +// private $id; +// private $shortTitle; +// private $subjects = array(); +// +// //------------------------------------ +// // +// // Beginning Constructor +// // +// //------------------------------------ +// +// public function __construct($id, $shortTitle) { +// $this->id = $id; +// $this->shortTitle = $shortTitle; +// } +// +// //------------------------------------ +// // +// // Beginning Get +// // +// //------------------------------------ +// +// public function getId() { +// return $this->id; +// } +// +// public function getShortTitle() { +// return $this->shortTitle; +// } +// +// public function getSubjects() { +// return $this->subjects; +// } +// +// //------------------------------------ +// // +// // Beginning Set +// // +// //------------------------------------ +// +// +// +// public function setId($id) { +// $this->id = $id; +// } +// +// public function setShortTitle($shortTitle) { +// $this->shortTitle = $shortTitle; +// } +// +// public function setSubject($subjects) { +// array_push($this->subjects, $subjects); +// } +// +//} +// +?> diff --git a/qtici_course/classes/ChapterDropFolder.php b/qtici_course/classes/ChapterDropFolder.php new file mode 100755 index 0000000000000000000000000000000000000000..971e4dad01ec8da54944c25689ffcc401735ce08 --- /dev/null +++ b/qtici_course/classes/ChapterDropFolder.php @@ -0,0 +1,39 @@ +<?php +class ChapterDropFolder extends Chapter { + + private $folders = array(); + + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct() { + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getChapterFolders() { + return $this->folders; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + public function setChapterFolders($folders) { + array_push($this->folders, $folders); + } + +} + +?> diff --git a/qtici_course/classes/ChapterLearningObject.php b/qtici_course/classes/ChapterLearningObject.php new file mode 100755 index 0000000000000000000000000000000000000000..f3d2c28c2d735543825c265e4493f6905c59bb8d --- /dev/null +++ b/qtici_course/classes/ChapterLearningObject.php @@ -0,0 +1,40 @@ +<?php +class ChapterLearningObjectives extends Chapter { + + private $learningObjectives; + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct($learningObjectives) { + $this->learningObjectives = $learningObjectives; + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getChapterLearningObjectives() { + return $this->learningObjectives; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + + public function setChapterLearningObjectives($learningObjectives) { + $this->learningObjectives = $learningObjectives; + } + +} + +?> diff --git a/qtici_course/classes/ChapterPage.php b/qtici_course/classes/ChapterPage.php new file mode 100755 index 0000000000000000000000000000000000000000..fc52988c77f2616d2b314922901bae2fe822624c --- /dev/null +++ b/qtici_course/classes/ChapterPage.php @@ -0,0 +1,40 @@ +<?php +class ChapterPage extends Chapter { + + private $htmlPage; + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct($htmlPage) { + $this->htmlPage = $htmlPage; + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getChapterPage() { + return $this->htmlPage; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + + public function setChapterPage($htmlPage) { + $this->htmlPage = $htmlPage; + } + +} + +?> diff --git a/qtici_course/classes/Course.php b/qtici_course/classes/Course.php new file mode 100755 index 0000000000000000000000000000000000000000..5ff0f272b80b862e93bfca6af7c943b33ef3e139 --- /dev/null +++ b/qtici_course/classes/Course.php @@ -0,0 +1,99 @@ +<?php +include_once 'Chapter.php'; +include_once 'ChapterPage.php'; +include_once 'ChapterDropFolder.php'; +include_once 'ChapterLearningObject.php'; +include_once 'Subject.php'; +include_once 'SubjectPage.php'; +include_once 'SubjectDropFolder.php'; +include_once 'SubjectLearningObject.php'; +//include_once 'SubjectTest.php'; +include 'Folder.php'; +class course { + + private $id; + private $shortTitle; + private $longTitle; + private $version; + private $type; + private $chapters = array(); + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct($id, $shortTitle, $longTitle, $version, $type) { + $this->id = $id; + $this->shortTitle = $shortTitle; + $this->longTitle = $longTitle; + $this->version = $version; + $this->type = $type; + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getId() { + return $this->id; + } + + public function getShortTitle() { + return $this->shortTitle; + } + + public function getLongTitle() { + return $this->longTitle; + } + + public function getVersion() { + return $this->version; + } + + public function getType() { + return $this->type; + } + + public function getChapters() { + return $this->chapters; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + + public function setId($id) { + $this->id = $id; + } + + public function setShortTitle($shortTitle) { + $this->shortTitle = $shortTitle; + } + + public function setLongTitle($longTitle) { + $this->longTitle = $longTitle; + } + + public function setVersion($version) { + $this->version = $version; + } + + public function setType($type) { + $this->type = $type; + } + + public function setChapter($chapters) { + array_push($this->chapters, $chapters); + } + +} + +?> diff --git a/qtici_course/classes/Folder.php b/qtici_course/classes/Folder.php new file mode 100755 index 0000000000000000000000000000000000000000..ca6f4bc71de32c9dbe232f0938fce6e513080d5d --- /dev/null +++ b/qtici_course/classes/Folder.php @@ -0,0 +1,79 @@ +<?php +class Folder { + + private $fileName; + private $fileLocation; + private $fileSize; + private $fileType; + private $fileModified; + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct($fileName ='', $fileLocation ='', $fileSize ='', $fileType ='', $fileModified ='') { + $this->fileName = $fileName; + $this->fileLocation = $fileLocation; + $this->fileSize = $fileSize; + $this->fileType = $fileType; + $this->fileModified = $fileModified; + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getFileName() { + return $this->fileName; + } + + public function getFileLocation() { + return $this->fileLocation; + } + + public function getFileSize() { + return $this->fileSize; + } + + public function getFileType() { + return $this->fileType; + } + + public function getFileModified() { + return $this->fileModified; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + + public function setFileName($fileName) { + $this->fileName = $fileName; + } + + public function setFileLocation($fileLocation) { + $this->fileLocation = $fileLocation; + } + + public function setFileSize($fileSize) { + $this->fileSize = $fileSize; + } + + public function setFileType($fileType) { + $this->fileType = $fileType; + } + + public function setFileModified($fileModified) { + $this->fileModified = $fileModified; + } + +} +?> diff --git a/qtici_course/classes/Subject.php b/qtici_course/classes/Subject.php new file mode 100755 index 0000000000000000000000000000000000000000..86dd9ddf34175914e51e5bc0d158bc6d9acceed4 --- /dev/null +++ b/qtici_course/classes/Subject.php @@ -0,0 +1,66 @@ +<?php +class Subject { + + private $id; + private $shortTitle; + private $type; + private $subjects = array(); + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct($id ='', $shortTitle ='', $type ='') { + $this->id = $id; + $this->shortTitle = $shortTitle; + $this->type = $type; + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getSubjectId() { + return $this->id; + } + + public function getSubjectShortTitle() { + return $this->shortTitle; + } + + public function getSubjectType() { + return $this->type; + } + public function getSubjects() { + return $this->subjects; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + + public function setSubjectId($id) { + $this->id = $id; + } + + public function setSubjectShortTitle($shortTitle) { + $this->shortTitle = $shortTitle; + } + + public function setSubjectType($type) { + $this->type = $type; + } + public function setSubject($subjects) { + array_push($this->subjects, $subjects); + } + +} +?> diff --git a/qtici_course/classes/SubjectDropFolder.php b/qtici_course/classes/SubjectDropFolder.php new file mode 100755 index 0000000000000000000000000000000000000000..38c537bc8d73fa8318210b4f2ba5ffe5aef33803 --- /dev/null +++ b/qtici_course/classes/SubjectDropFolder.php @@ -0,0 +1,39 @@ +<?php +class SubjectDropFolder extends Subject { + + private $folders = array(); + + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct() { + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getSubjectFolders() { + return $this->folders; + } + + //------------------------------------ + // + // Beginning Set + //ContentType --> hook_node_info (hook_node) + //------------------------------------ + + + public function setSubjectFolders($folders) { + array_push($this->folders, $folders); + } + +} + +?> \ No newline at end of file diff --git a/qtici_course/classes/SubjectLearningObject.php b/qtici_course/classes/SubjectLearningObject.php new file mode 100755 index 0000000000000000000000000000000000000000..1b823dba893c860bcd54cb256fdc3bbc033876fb --- /dev/null +++ b/qtici_course/classes/SubjectLearningObject.php @@ -0,0 +1,40 @@ +<?php +class SubjectLearningObjectives extends Subject { + + private $learningObjectives; + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct($learningObjectives) { + $this->learningObjectives = $learningObjectives; + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getSubjectLearningObjectives() { + return $this->learningObjectives; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + + public function setSubjectLearningObjectives($learningObjectives) { + $this->learningObjectives = $learningObjectives; + } + +} + +?> diff --git a/qtici_course/classes/SubjectPage.php b/qtici_course/classes/SubjectPage.php new file mode 100755 index 0000000000000000000000000000000000000000..2590317b114170f2a80b0c76e3552d337a8e65a8 --- /dev/null +++ b/qtici_course/classes/SubjectPage.php @@ -0,0 +1,40 @@ +<?php +class SubjectPage extends Subject { + + private $htmlPage; + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct($htmlPage ="") { + $this->htmlPage = $htmlPage; + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getSubjectPage() { + return $this->htmlPage; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + + public function setSubjectPage($htmlPage) { + $this->htmlPage = $htmlPage; + } + +} + +?> diff --git a/qtici_course/classes/SubjectTest.php b/qtici_course/classes/SubjectTest.php new file mode 100755 index 0000000000000000000000000000000000000000..5a903622d6e7445e5b971db6820e2efb856d051f --- /dev/null +++ b/qtici_course/classes/SubjectTest.php @@ -0,0 +1,40 @@ +<?php +class SubjectTest extends Subject { + + private $test; + + //------------------------------------ + // + // Beginning Constructor + // + //------------------------------------ + + public function __construct($test) { + $this->test= $test; + } + + //------------------------------------ + // + // Beginning Get + // + //------------------------------------ + + public function getSubjectTest() { + return $this->test; + } + + //------------------------------------ + // + // Beginning Set + // + //------------------------------------ + + + + public function setSubjectTest($test) { + $this->test = $test; + } + +} + +?> diff --git a/qtici_course/css/treeView.css b/qtici_course/css/treeView.css new file mode 100755 index 0000000000000000000000000000000000000000..377b624192779580121c3be1d0d5756a6d88895f --- /dev/null +++ b/qtici_course/css/treeView.css @@ -0,0 +1,47 @@ +.demo { width:255px; float:left; margin:0; border:1px solid gray; background:white; overflow:auto; } +.code { width:475px; float:right; margin:0 0 10px 0; border:1px solid gray; font-size:12px; } +pre { display:block; } +.code_f { border:1px solid gray; margin-bottom:1em; } +.syntaxhighlighter { margin:0 0 0 0 !important; padding:0 !important; line-height:18px; } + +.log { padding:4px; border:1px solid gray; margin-bottom:1em; } +.button { display:block; margin-bottom:0.5em; } +.arguments { margin:0em 1em; padding:0; list-style-type:none; } +.arguments .tp { padding:0 0 0 0; float:left; width:70px; } +.arguments strong { display:block; } + +.api h3 { margin-left:-10px; color:black; font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-weight: normal !important; font-size:14px; margin-top:2em; border-top:1px solid; width:780px; } +.api .arguments li strong { color:black; font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-weight: normal !important; font-size:13px; } + +.configuration h3 { margin-left:-10px; color:black; font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-weight: normal !important; font-size:14px; margin-top:2em; border-top:1px solid; width:780px; } +.note { background:#ffffee; padding:10px 20px; border:1px solid #333; -moz-border-radius:5px; border-radius:5px; -webkit-border-radius:5px; margin-bottom:15px; text-align:center; font-weight:bold; } + +.plugins, .demos { margin:0 auto 20px auto; } + +ul.columns { list-style-type:none; width:700px; margin:0px auto 15px auto; padding:0; overflow:hidden; } +ul.columns li { float:left; margin:0; padding:0 0 0 0px; line-height:18px; width:345px; } +ul.demos li, ul.plugins li { width:220px; text-align:left; padding:5px 0; } +ul.demos li a, ul.plugins li a { text-decoration:none; color:#3B5998; } +ul.demos li a:hover, ul.plugins li a:hover { text-decoration:underline; } +ul.plugins li p { text-align:left; font-size:9px; color:#333; margin:0 5px 0 0; } + +ul.demos li { width:auto; } + +.demo, .demo input, .jstree-dnd-helper, #vakata-contextmenu { font-size:10px; font-family:Verdana; } + +#demo_body .demo, #demo_body .code { width:auto; float:none; clear:both; margin:10px auto; } +#demo_body .code { margin-bottom:20px; } + +ul.jstree { width:700px; margin:0px auto 15px auto; padding:0; } +ul.jstree li { margin:0; padding:2px 0; } +ul.jstree li a { color:#3B5998; text-decoration:underline; } + +#mcs3_container{position:absolute; left:640px; top:20px; margin:0; width:280px; height:96%; padding:0 10px; border-bottom:1px solid #666; -moz-border-radius:4px; -khtml-border-radius:4px; -webkit-border-radius:4px; border-radius:4px; background:#6D5843; box-shadow:inset 0 0 20px #000; -moz-box-shadow:inset 0 0 20px #000; -webkit-box-shadow:inset 0 0 20px #000;} +#mcs3_container .customScrollBox{position:relative; height:100%; overflow:hidden;} +#mcs3_container .customScrollBox .container{position:relative; width:240px; top:0; float:left;} +#mcs3_container .customScrollBox .content{clear:both;} +#mcs3_container .customScrollBox .content p{padding:10px 5px 10px 15px; margin:0; color:#31231E; font-family:Verdana, Geneva, sans-serif; font-size:13px; line-height:20px;} +#mcs3_container .customScrollBox img{border:3px solid #31231E; margin:0 0 0 15px;} +#mcs3_container .dragger_container{position:relative; width:0px; height:33%; float:left; margin:35px 0 0 25px; border-left:1px solid #31231E; border-right:1px solid #8E7757; cursor:pointer} +#mcs3_container .dragger{position:absolute; width:9px; height:60px; background:#31231E; margin-left:-5px; overflow:hidden; cursor:pointer; -moz-border-radius:6px; -khtml-border-radius:6px; -webkit-border-radius:6px; border-radius:6px;} +#mcs3_container .dragger_pressed{position:absolute; width:9px; height:60px; background:#31231E; margin-left:-5px; overflow:hidden; cursor:pointer; -moz-border-radius:6px; -khtml-border-radius:6px; -webkit-border-radius:6px; border-radius:6px;} diff --git a/qtici_course/forms.inc b/qtici_course/forms.inc new file mode 100644 index 0000000000000000000000000000000000000000..c6a926a4852a7ef3c527f87642dbd4cf6c305a95 --- /dev/null +++ b/qtici_course/forms.inc @@ -0,0 +1,38 @@ +<?php + +/** + * @file - All forms go in this file + */ + +/** + * This function will trigger curse_overview() + */ +function qtici_cursus_overview($form, &$form_state) { + + $form['module_cursus_overview'] = array( + '#type' => 'markup', + '#markup' => cursus_overview(), + ); + + return $form; +} + +function show_course($from, &$form_state) { + return array( + 'module_course' => array( + '#type' => 'markup', + '#markup' => course($from, $form_state), + ), + ); +} + +function qtici_conf($form, &$form_state) { + if (user_access('teacher')) { + return array( + 'qtici_conf' => array( + '#type' => 'markup', + '#markup' => conf($form, $form_state), + ), + ); + } +} diff --git a/qtici_course/functions.inc b/qtici_course/functions.inc new file mode 100644 index 0000000000000000000000000000000000000000..e188f761aa9177e0e027c6e519f923bcdd2349c3 --- /dev/null +++ b/qtici_course/functions.inc @@ -0,0 +1,747 @@ +<?php + +/** + * @file - Diverse functions + */ + +function getSubjectsDb($subjects, $parent_chapter_id = NULL, $parent_subject_id = NULL) { + + if ($subjects == null) + return; + + foreach ($subjects as $subjectObject) { + //doe uw ding + $subject_general_info_id = db_insert('qtici_course_general_info') + ->fields(array( + 'general_id' => $subjectObject->getSubjectId(), + 'short_title' => $subjectObject->getSubjectShortTitle(), + 'type' => $subjectObject->getSubjectType(), + 'publish_status' => 1, + )) + ->execute(); + + $subject_id = db_insert('qtici_course_subject') + ->fields(array( + 'general_info_id' => $subject_general_info_id, + 'chapter_id' => $parent_chapter_id, + 'subject_id' => $parent_subject_id, + 'status' => 0, + )) + ->execute(); + + + $subject_function_id = db_insert('qtici_course_function') + ->fields(array( + 'subject_id' => $subject_id, + )) + ->execute(); + + switch ($subjectObject->getSubjectType()) { + case "iqtest": + case "iqself": + case "iqsurv": + break; + case "en": + $subject_learning_object_id = db_insert('qtici_course_learning_object') + ->fields(array( + 'learning_object' => $subjectObject->getSubjectLearningObjectives(), + 'function_id' => $subject_function_id, + )) + ->execute(); + break; + + case "bc": + + foreach ($subjectObject->getSubjectFolders() as $FolderObject) { + $subject_folder_id = db_insert('qtici_course_folder') + ->fields(array( + 'name' => $FolderObject->getFileName(), + 'location' => $FolderObject->getFileLocation(), + 'type' => $FolderObject->getFileType(), + 'size' => $FolderObject->getFileSize(), + 'modified' => $FolderObject->getFileModified(), + )) + ->execute(); + + $subject_drop_folder_id = db_insert('qtici_course_drop_folder') + ->fields(array( + 'function_id' => $subject_function_id, + 'folder_id' => $subject_folder_id, + )) + ->execute(); + } + + + break; + + case "sp": + case "st": + $subject_page_id = db_insert('qtici_course_page') + ->fields(array( + 'location' => $subjectObject->getSubjectPage(), + 'function_id' => $subject_function_id, + )) + ->execute(); + break; + } + + + getSubjectsDb($subjectObject->getSubjects(), NULL, $subject_id); + }; +} + +/** + * This function will delete, publish and unpublish the courses, + * if action is "delete_course" than you will delete the course + * if action is checkbox_course than you will receive an array of id's and triggers the function 'checkbox_and_publish_course' which will publishes or unpublished the course + * if action is (un)publish_course, the function 'checkbox_and_publish_course' will be triggered, and it will publishes or unpublished the course + * if action is checkbox than you will receive an array of id's and triggers the function 'checkbox_and_publish_config_course' which will publishes or unpublished chapters or subjects + * if action is nor of the items above :), the function 'checkbox_and_publish_config_course' will be triggered, and it will publishes or unpublished the chapter or subject + */ +function course_conf_page_callback() { + + //object from the class test for accessing his functions + $testObj = new Test(); + + if (user_access('teacher')) { + $course_conf_info = $_POST["var"]; + $checkArray = $course_conf_info[0]; + $action = $course_conf_info[1]; + if ($action == 'delete_course') { + + $alsoDeleteTests = $course_conf_info[3]; + + foreach ($checkArray as $data) { + list($id, $i) = explode("-", $data); + + if ($alsoDeleteTests == 'true') { + $results = find_iqTest($id); + foreach ($results as $olatTestid) { + $testObj->deleteTestByTestIDOROlatID(null, $olatTestid); + } + } + + $query = db_select('qtici_course', 'c'); + $query + ->join('qtici_course_general_info', 'g', 'c.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('short_title', 'publish_status')) + ->fields('c', array('id', 'status', 'general_info_id', 'filepath')) + ->condition('c.id', $id, '=') + ; + $result = $query->execute(); + $path = drupal_get_path('module', 'qtici'); + foreach ($result as $item) { + recursiveDelete($item->id); + + $course_deleted = db_delete('qtici_course') + ->condition('id', $item->id) + ->execute(); + $general_info_course_deleted = db_delete('qtici_course_general_info') + ->condition('id', $item->general_info_id) + ->execute(); + + //delete files in /upload/files + $filepath = $item->filepath; + rrmdir('sites/default/files/qtici/' . $filepath); + } + } + } else { + if ($action == 'checkbox_course' || $action == '(un)publish_course') { + if ($action == 'checkbox_course') { + foreach ($checkArray as $array) { + $data = $array; + list($id, $i) = explode("-", $data); + checkbox_and_publish_course($i, $id, 0); + } + } else if ($action == '(un)publish_course') { + $id = $checkArray; + $i = $course_conf_info[2]; + checkbox_and_publish_course($i, $id, 1); + } + } else { + + if ($action == 'checkbox') { + $id_course = $course_conf_info[3]; + $Rev_checkArray = array_reverse($checkArray); + foreach ($Rev_checkArray as $array) { + $data = $array; + list($id, $lvl, $i, $id_course, $parents) = explode("-", $data); + + checkbox_and_publish_config_course($i, $id, $lvl, $id_course, $parents); + } + } else { + $id = $checkArray; + $lvl = $action; + $i = $course_conf_info[2]; + $id_course = $course_conf_info[3]; + $parents = $course_conf_info[4]; + + checkbox_and_publish_config_course($i, $id, $lvl, $id_course, $parents); + } + } + } + } + //exit; +} + +/** + * This function will publish or unpublish a course and is triggered by course_conf_page_callback + */ +function checkbox_and_publish_course($i = NULL, $id = NULL, $ch = NULL) { + drupal_add_css(drupal_get_path('module', 'qtici') . '/css/images.css'); + + if (user_access('teacher')) { + $query = db_select('qtici_course', 'c'); + $query + ->join('qtici_course_general_info', 'g', 'c.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('id', 'publish_status')) + ->fields('c', array('status')) + ->condition('c.id', $id, '=') + ; + $result = $query->execute(); + + foreach ($result as $item) { + if ($item->publish_status == 0) { + + $path = drupal_get_path('module', 'qtici'); + db_update('qtici_course_general_info') + ->fields(array('publish_status' => 1)) + ->condition('id', $item->id, '=') + ->execute(); + echo '<script type="text/javascript">'; + if ($item->status == 0) { + echo '$("#img_publ_' . $i . '").attr(\'class\', \'img_published\');'; + echo '$("#img_publ_' . $i . '").attr(\'title\', \'' . t('Published') . '\');'; + } else { + echo '$("#img_publ_' . $i . '").attr(\'class\', \'img_halfpublished\');'; + echo '$("#img_publ_' . $i . '").attr(\'title\', \'' . t('Published met unpublished items') . '\');'; + } + echo '$("#selectCourseArray' . $i . '").each(function(){ this.checked = false; });'; + echo '$("#img_publ_' . $i . '").next().text("1");'; + echo '</script>'; + } else { + db_update('qtici_course_general_info') + ->fields(array('publish_status' => 0)) + ->condition('id', $item->id, '=') + ->execute(); + $path = drupal_get_path('module', 'qtici'); + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $i . '").attr(\'class\', \'img_unpublished\');'; + echo '$("#img_publ_' . $i . '").attr(\'title\', \'' . t('Unpublished') . '\');'; + echo '$("#selectCourseArray' . $i . '").each(function(){ this.checked = false; });'; + echo '$("#img_publ_' . $i . '").next().text("0");'; + echo '</script>'; + } + } + } +} + +/** + * This function will publish or unpublish a chapter or subject and is triggered by course_conf_page_callback + */ +function checkbox_and_publish_config_course($i = NULL, $id = NULL, $lvl = NULL, $id_course = NULL, $parents = NULL) { + drupal_add_css(drupal_get_path('module', 'qtici') . '/css/images.css'); + + if (user_access('teacher')) { + if ($lvl == 1) { + $query = db_select('qtici_course_chapter', 'ch'); + $query + ->join('qtici_course_general_info', 'g', 'ch.general_info_id = g.id'); //JOIN node with users + $query + ->fields('ch', array('status')) + ->fields('g', array('id', 'publish_status')) + ->condition('ch.id', $id, '=') + ; + } else { + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); //JOIN node with users + $query + ->fields('su', array('status')) + ->fields('g', array('id', 'publish_status')) + ->condition('su.id', $id, '=') + ; + } + + $result = $query->execute(); + $path = drupal_get_path('module', 'qtici'); + foreach ($result as $item) { + + + $query = db_select('qtici_course', 'c'); + $query + ->fields('c', array('status')) + ->condition('c.id', $id_course, '=') + ; + $result_course = $query->execute(); + if ($item->publish_status == 0) { + db_update('qtici_course_general_info') + ->fields(array('publish_status' => 1)) + ->condition('id', $item->id, '=') + ->execute(); + foreach ($result_course as $res_course) { + $sum = $res_course->status - 1; + + db_update('qtici_course') + ->fields(array('status' => $sum)) + ->condition('id', $id_course, '=') + ->execute(); + } + $new_status = NULL; + $publish_status = 0; + + if ($lvl > 2) { + for ($g = 0; $g < $lvl - 2; $g++) { + $query = db_select('qtici_course_subject', 'su'); + $query + ->fields('su', array('subject_id ')) + ->condition('su.id', $id, '=') + ; + $result = $query->execute(); + foreach ($result as $itemStat) { + $id = $itemStat->subject_id; + } + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); + $query + ->fields('g', array('publish_status')) + ->fields('su', array('status ')) + ->condition('su.id', $id, '=') + ; + $result = $query->execute(); + foreach ($result as $itemStat) { + $new_status = $itemStat->status; + $publish_status = $itemStat->publish_status; + } + $new_status = $new_status - 1; + db_update('qtici_course_subject') + ->fields(array('status' => $new_status)) + ->condition('id', $id, '=') + ->execute(); + $parent = explode("-", $parents); + $parent = array_reverse($parent); + // echo "<script>alert('$publish_status')</script>"; + if ($new_status != 0 && $publish_status != 0) { + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $parent[$g] . '").attr(\'class\', \'img_halfpublished\');'; + echo '$("#img_publ_' . $parent[$g] . '").attr(\'title\', \'' . t('Published met unpublished items') . '\');'; + // echo '$("#selectCourseArray' . $parent[$g] . '").each(function(){ this.checked = false; });'; + echo '</script>'; + } else if ($new_status == 0 && $publish_status != 0) { + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $parent[$g] . '").attr(\'class\', \'img_published\');'; + echo '$("#img_publ_' . $parent[$g] . '").attr(\'title\', \'' . t('Published') . '\');'; + // echo '$("#selectCourseArray' . $parent[$g] . '").each(function(){ this.checked = false; });'; + echo '</script>'; + } + } + } + if ($lvl > 1) { + $query = db_select('qtici_course_subject', 'su'); + $query + ->fields('su', array('chapter_id ')) + ->condition('su.id', $id, '=') + ; + $result = $query->execute(); + foreach ($result as $itemStat) { + $id = $itemStat->chapter_id; + } + $query = db_select('qtici_course_chapter', 'ch'); + $query + ->join('qtici_course_general_info', 'g', 'ch.general_info_id = g.id'); + $query + ->fields('g', array('publish_status')) + ->fields('ch', array('status')) + ->condition('ch.id', $id, '=') + ; + $result = $query->execute(); + foreach ($result as $itemStat) { + $new_status = $itemStat->status; + $publish_status = $itemStat->publish_status; + } + $new_status = $new_status - 1; + db_update('qtici_course_chapter') + ->fields(array('status' => $new_status)) + ->condition('id', $id, '=') + ->execute(); + $parent = explode("-", $parents); + $parent = array_reverse($parent); + + if ($new_status != 0 && $publish_status != 0) { + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $parent[count($parent) - 1] . '").attr(\'class\', \'img_halfpublished\');'; + echo '$("#img_publ_' . $parent[count($parent) - 1] . '").attr(\'title\', \'' . t('Published met unpublished items') . '\');'; + // echo '$("#selectCourseArray' . $parent[count($parent)-2] . '").each(function(){ this.checked = false; });'; + echo '</script>'; + } else if ($new_status == 0 && $publish_status != 0) { + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $parent[count($parent) - 1] . '").attr(\'class\', \'img_published\');'; + echo '$("#img_publ_' . $parent[count($parent) - 1] . '").attr(\'title\', \'' . t('Published') . '\');'; + // echo '$("#selectCourseArray' . $i . '").each(function(){ this.checked = false; });'; + echo '</script>'; + } + } + if ($item->status == 0) { + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $i . '").attr(\'class\', \'img_published\');'; + echo '$("#img_publ_' . $i . '").attr(\'title\', \'' . t('Published') . '\');'; + echo '$("#selectCourseArray' . $i . '").each(function(){ this.checked = false; });'; + echo '</script>'; + } else { + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $i . '").attr(\'class\', \'img_halfpublished\');'; + echo '$("#img_publ_' . $i . '").attr(\'title\', \'' . t('Published met unpublished items') . '\');'; + echo '$("#selectCourseArray' . $i . '").each(function(){ this.checked = false; });'; + echo '</script>'; + } + } else { + db_update('qtici_course_general_info') + ->fields(array('publish_status' => 0)) + ->condition('id', $item->id, '=') + ->execute(); + foreach ($result_course as $res_course) { + $sum = $res_course->status + 1; + db_update('qtici_course') + ->fields(array('status' => $sum)) + ->condition('id', $id_course, '=') + ->execute(); + } + $new_status = NULL; + $publish_status = 0; + if ($lvl > 2) { + for ($g = 0; $g < $lvl - 2; $g++) { + $query = db_select('qtici_course_subject', 'su'); + $query + ->fields('su', array('subject_id ')) + ->condition('su.id', $id, '=') + ; + $result = $query->execute(); + foreach ($result as $itemStat) { + $id = $itemStat->subject_id; + } + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); + $query + ->fields('g', array('publish_status')) + ->fields('su', array('status ')) + ->condition('su.id', $id, '=') + ; + $result = $query->execute(); + foreach ($result as $itemStat) { + $new_status = $itemStat->status; + $publish_status = $itemStat->publish_status; + } + $new_status = $new_status + 1; + db_update('qtici_course_subject') + ->fields(array('status' => $new_status)) + ->condition('id', $id, '=') + ->execute(); + $parent = explode("-", $parents); + $parent = array_reverse($parent); + // echo "<script>alert('$publish_status')</script>"; + if ($new_status != 0 && $publish_status != 0) { + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $parent[$g] . '").attr(\'class\', \'img_halfpublished\');'; + echo '$("#img_publ_' . $parent[$g] . '").attr(\'title\', \'' . t('Published met unpublished items') . '\');'; + echo '$("#img_publ_' . $parent[$g] . '").attr(\'alt\', \'' . t('published met unpublished items') . '\');'; + // echo '$("#selectCourseArray' . $parent[$g] . '").each(function(){ this.checked = false; });'; + echo '</script>'; + } + } + } + if ($lvl > 1) { + $query = db_select('qtici_course_subject', 'su'); + $query + ->fields('su', array('chapter_id ')) + ->condition('su.id', $id, '=') + ; + $result = $query->execute(); + foreach ($result as $itemStat) { + $id = $itemStat->chapter_id; + } + $query = db_select('qtici_course_chapter', 'ch'); + $query + ->join('qtici_course_general_info', 'g', 'ch.general_info_id = g.id'); + $query + ->fields('g', array('publish_status')) + ->fields('ch', array('status ')) + ->condition('ch.id', $id, '=') + ; + $result = $query->execute(); + foreach ($result as $itemStat) { + $new_status = $itemStat->status; + $publish_status = $itemStat->publish_status; + } + $new_status = $new_status + 1; + db_update('qtici_course_chapter') + ->fields(array('status' => $new_status)) + ->condition('id', $id, '=') + ->execute(); + $parent = explode("-", $parents); + $parent = array_reverse($parent); + if ($new_status != 0 && $publish_status != 0) { + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $parent[count($parent) - 1] . '").attr(\'class\', \'img_halfpublished\');'; + echo '$("#img_publ_' . $parent[count($parent) - 1] . '").attr(\'title\', \'' . t('Published met unpublished items') . '\');'; + // echo '$("#selectCourseArray' . $parent[count($parent)-2] . '").each(function(){ this.checked = false; });'; + echo '</script>'; + } + } + + + echo '<script type="text/javascript">'; + echo '$("#img_publ_' . $i . '").attr(\'class\', \'img_unpublished\');'; + echo '$("#img_publ_' . $i . '").attr(\'title\', \'' . t('Unpublished') . '\');'; + echo '$("#selectCourseArray' . $i . '").each(function(){ this.checked = false; });'; +// $parents = explode("-", $parents); +// foreach ($parents as $parent) { +// echo '$("#img_publ_' . $parent . '").attr(\'src\', \'' . trim($path) . '/css/images/halfpublished.png\');'; +// echo '$("#img_publ_' . $parent . '").attr(\'title\', \'' . t('Published met unpublished items') . '\');'; +// echo '$("#img_publ_' . $parent . '").attr(\'alt\', \'' . t('published met unpublished items') . '\');'; +// }; + echo '</script>'; + } + + //$new_id = NULL; + } + } +} + +/** + * + * this recursive function will delete all the chapters, ans subjects and triggers the function 'deleteFunction' for deleting the chapters or course function such as page, dropfolder and learning object + */ +function recursiveDelete($course_id = NULL, $chapter_id = NULL, $subject_id = NULL) { + if (user_access('teacher')) { + $type = 0; + if ($course_id != NULL) { + $type = 1; + $query = db_select('qtici_course_chapter', 'ch'); + $query + ->fields('ch', array('id', 'general_info_id')) + ->condition('ch.course_id', $course_id, '=') + ; + } else if ($chapter_id != NULL) { + $type = 2; + $query = db_select('qtici_course_subject', 'su'); + $query + ->fields('su', array('id', 'general_info_id')) + ->condition('su.chapter_id', $chapter_id, '='); + } else { + $type = 3; + $query = db_select('qtici_course_subject', 'su'); + $query + ->fields('su', array('id', 'general_info_id')) + ->condition('su.subject_id', $subject_id, '='); + } + $result = $query->execute(); + + foreach ($result as $item) { + + $item_id = $item->id; + $general_info_id = $item->general_info_id; + if ($course_id != NULL) { + $query = db_select('qtici_course_subject', 'su'); + $query + ->fields('su', array('id', 'general_info_id')) + ->condition('su.chapter_id', $item_id, '='); + } else { + $query = db_select('qtici_course_subject', 'su'); + $query + ->fields('su', array('id', 'general_info_id')) + ->condition('su.subject_id', $item_id, '='); + } + $amount = $query->execute(); + + if ($amount->rowCount() > 0) { + + if ($type == 1) { + deleteFunction($item_id); + $chapter_deleted = db_delete('qtici_course_chapter') + ->condition('id', $item_id) + ->execute(); + } else if ($type > 1) { + deleteFunction(NULL, $item_id); + $subject_deleted = db_delete('qtici_course_subject') + ->condition('id', $item_id) + ->execute(); + } + $general_info_deleted = db_delete('qtici_course_general_info') + ->condition('id', $general_info_id) + ->execute(); + + if ($course_id != NULL) { + $htmlArray = recursiveDelete(NULL, $item_id, NULL); + } else { + //echo "<script>alert(' gen id $general_info_id - id $item_id - type = $type - aantal rijen ".$amount->rowCount()."')</script>"; + $htmlArray = recursiveDelete(NULL, NULL, $item_id); + } + } else { + if ($type == 1) { + deleteFunction($item_id); + $chapter_deleted = db_delete('qtici_course_chapter') + ->condition('id', $item_id) + ->execute(); + } else if ($type > 1) { + deleteFunction(NULL, $item_id); + $subject_deleted = db_delete('qtici_course_subject') + ->condition('id', $item_id) + ->execute(); + } + $general_info_deleted = db_delete('qtici_course_general_info') + ->condition('id', $general_info_id) + ->execute(); + } + } + } + return; +} + +/** + * + * this function will delete the page, dropfolder or learning object + */ +function deleteFunction($chapter_id = NULL, $subject_id = NULL) { + if (user_access('teacher')) { + $function_id = NULL; + if (isset($chapter_id)) { + $query = db_select('qtici_course_function', 'fu'); + $query + ->fields('fu', array('id')) + ->condition('fu.chapter_id', $chapter_id, '='); + } else { + $query = db_select('qtici_course_function', 'fu'); + $query + ->fields('fu', array('id')) + ->condition('fu.subject_id', $subject_id, '='); + } + $result = $query->execute(); + foreach ($result as $item) { + $function_id = $item->id; + + $function_deleted = db_delete('qtici_course_function') + ->condition('id', $function_id) + ->execute(); + } + $query = db_select('qtici_course_learning_object', 'lo'); + $query + ->fields('lo', array('id')) + ->condition('lo.function_id', $function_id, '='); + $result = $query->execute(); + if ($result->rowCount() > 0) { + + $learning_object_deleted = db_delete('qtici_course_learning_object') + ->condition('function_id', $function_id) + ->execute(); + } else { + $query = db_select('qtici_course_page', 'p'); + $query + ->fields('p', array('id')) + ->condition('p.function_id', $function_id, '='); + $result = $query->execute(); + if ($result->rowCount() > 0) { + + $page_deleted = db_delete('qtici_course_page') + ->condition('function_id', $function_id) + ->execute(); + ///////////////////////////////////////////////// + } else { + $query = db_select('qtici_course_drop_folder', 'df'); + $query + ->fields('df', array('id', 'folder_id')) + ->condition('df.function_id', $function_id, '='); + $result = $query->execute(); + if ($result->rowCount() > 0) { + foreach ($result as $res) { + + $folder_deleted = db_delete('qtici_course_folder') + ->condition('id', $res->folder_id) + ->execute(); + } + + $dropfolder_deleted = db_delete('qtici_course_drop_folder') + ->condition('function_id', $function_id) + ->execute(); + } + } + } + } + return; +} + +function find_iqTest($course_id) { + $qti_string = ""; + $query = db_select('qtici_course', 'c'); + $query->join('qtici_course_general_info', 'g', 'c.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('general_id', 'type')) + ->fields('c', array('id')) + ->condition('c.id', $course_id, '='); + $result = $query->execute(); + foreach ($result as $item) { + if ($item->type == 'iqtest' || $item->type == 'iqself' || $item->type == 'iqsurv') { + $qti_string .= $item->general_id; + } + $query = db_select('qtici_course_chapter', 'ch'); + $query->join('qtici_course_general_info', 'g', 'ch.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('general_id', 'type')) + ->fields('ch', array('id')) + ->condition('ch.course_id', $item->id, '='); + $result_chapter = $query->execute(); + foreach ($result_chapter as $item_chapter) { + if ($item_chapter->type == 'iqtest' || $item_chapter->type == 'iqself' || $item_chapter->type == 'iqsurv') { + $qti_string .= '+' . $item_chapter->general_id; + } + + $query = db_select('qtici_course_subject', 's'); + $query->join('qtici_course_general_info', 'g', 's.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('general_id', 'type')) + ->fields('s', array('id')) + ->condition('s.chapter_id', $item_chapter->id, '='); + $result_subject = $query->execute(); + foreach ($result_subject as $item_subject) { + + + $recursive_qti = recursive_qti($item_subject->id, $item_subject->general_id, null, $item_subject->type); + + $qti_string .= "$recursive_qti"; + } + } + } + if (substr($qti_string, 0, 1) == '+') { + $qti_string = substr($qti_string, 1); + } + return explode("+", $qti_string); +} + +function recursive_qti($id, $general_id, $qti_string = NULL, $type = NULL) { + $qti_string = $qti_string; + $type = $type; + if ($type == 'iqtest' || $type == 'iqself' || $type == 'iqsurv') { + $qti_string .= '+' . $general_id; + } + $query = db_select('qtici_course_subject', 'su'); + $query->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); + $query + ->fields('g', array('general_id', 'type')) + ->fields('su', array('id')) + ->condition('su.subject_id', $id, '='); + $result_subject = $query->execute(); + + if ($result_subject->rowCount() > 0) { + foreach ($result_subject as $item) { + + $qti_string .= '' . recursive_qti($item->id, $item->general_id, $qti_string, $item->type); + + return "$qti_string"; + } + } else { + return "$qti_string"; + } +} diff --git a/qtici_course/object_course_parser.php b/qtici_course/object_course_parser.php new file mode 100755 index 0000000000000000000000000000000000000000..a912753ba1c535141a56ab889bbadd4ae4e1aeb2 --- /dev/null +++ b/qtici_course/object_course_parser.php @@ -0,0 +1,256 @@ +<?php + +/** + * + * This script converts courses from olat into an object. + * + * Dieter Verbeemen + */ +function parseCourseObject($filename) { + // $course = 85235053647606; + // $path = "/opt/olat/olatdata/bcroot/course"; + $pathCourse = substr("$filename", 0, -16); + + include_once 'classes/Course.php'; + $doc = new DOMDocument(); + if (file_exists($filename)) { + $doc->loadXML(file_get_contents($filename)); + $xpaths = simplexml_load_file($filename, 'SimpleXMLElement', LIBXML_NOCDATA); + } + else { + exit('Failed to open ' . $filename); + } + +// Course + + $result = $xpaths->xpath('/org.olat.course.Structure'); + $item = $result[0]; + $courseObject = new course( + isset($item->rootNode->ident) ? (string) $item->rootNode->ident : null, + isset($item->rootNode->shortTitle) ? (string) $item->rootNode->shortTitle : null, + isset($item->rootNode->longTitle) ? (string) $item->rootNode->longTitle : null, + isset($item->version) ? (string) $item->version : null, + isset($item->rootNode->type) ? (string) $item->rootNode->type : null); + +// Chapters + //// $chapters = $xpaths->xpath("/org.olat.course.Structure/rootNode/children/*[type = 'st' or type = 'sp' or type = 'bc' or type = 'iqtest']"); +$chapters = $xpaths->xpath("/org.olat.course.Structure/rootNode/children/*[type = 'st' or type = 'sp' or type = 'bc' or type = 'en' or type = 'iqtest' or type = 'iqself' or type = 'iqsurv']"); + foreach ($chapters as $item) { + // if noPage = 0 at the end, than the type would be sp or st, and have no page in it, + $noPage = 0; + switch ($item->type) { + case "iqtest": + case "iqself": + case "iqsurv": + $noPage ++; + $chapterObject = new Chapter; + break; + + case "en": + $noPage ++; + $chapterLearningObject = $xpaths->xpath("//*[ident = '$item->ident']/learningObjectives"); + foreach ($chapterLearningObject as $chapterLearningObjectItems) { + $chapterLearningObjectItem = (string)$chapterLearningObjectItems; + } + + $chapterObject = new ChapterLearningObjectives((string)$chapterLearningObjectItem); + break; + + case "bc": + // if noPage = 0 at the end, than the type would be sp or st, and have no page in it, + $noPage ++; + $chapterObject = new ChapterDropFolder(); + $course_map = getDirectoryList("$pathCourse" . "export/$item->ident"); + + for ($i = 0; count($course_map) > $i; $i++) { + $location = "$pathCourse" . "export/$item->ident/$course_map[$i]"; + $FolderObject = new Folder( + (string) $course_map[$i], + (string) $location, + (string) filesize($location), + (string) filetype($location), + (string) date("F d Y H:i:s.", filemtime($location))); + $chapterObject->setChapterFolders($FolderObject); + } + + + break; + + case "sp": + $chapterPagePath = $xpaths->xpath("//*[ident = '$item->ident']/moduleConfiguration/config//string[starts-with(.,'/')]"); + + foreach ($chapterPagePath as $chapterPage) { + $chapterPageItem = $chapterPage; + $noPage ++; + } + if (!empty($chapterPageItem)) { + $chapterObject = new ChapterPage((string) $chapterPageItem); + } + break; + case "st": + $chapterPagePath = $xpaths->xpath("//*[ident = '$item->ident']/moduleConfiguration/config//string[starts-with(.,'/')]"); + + foreach ($chapterPagePath as $chapterPage) { + $chapterPageItem = $chapterPage; + $noPage ++; + } + if (!empty($chapterPageItem)) { + $chapterObject = new ChapterPage((string) $chapterPageItem); + } + else { + $emptyHTML = ""; + $chapterObject = new ChapterPage($emptyHTML); + $noPage ++; + } + break; + } + + if($noPage != 0){ + $chapterObject->setId(isset($item->ident) ? (string) $item->ident : null); + $chapterObject->setShortTitle(isset($item->shortTitle) ? (string) $item->shortTitle : null); + $chapterObject->setType(isset($item->type) ? (string) $item->type : null); + $chapterObject->setVisibilityBeginDate(isset($item->preConditionVisibility->easyModeBeginDate) ? (string) $item->preConditionVisibility->easyModeBeginDate : null); + $chapterObject->setVisibilityEndDate(isset($item->preConditionVisibility->easyModeEndDate) ? (string) $item->preConditionVisibility->easyModeEndDate : null); + $chapterObject->setAccessBeginDate(isset($item->preConditionAccess->easyModeBeginDate) ? (string) $item->preConditionAccess->easyModeBeginDate : null); + $chapterObject->setAccessEndDate(isset($item->preConditionAccess->easyModeEndDate) ? (string) $item->preConditionAccess->easyModeEndDate : null); + + //Subjects + getSubjects($chapterObject, $item->ident, $xpaths, $pathCourse); + $courseObject->setChapter($chapterObject); + }} + return $courseObject; +} + +function getSubjects(&$object, $id, $xpaths, $pathCourse) { + // if noPag = 0 at the end, than the type would be sp or st, and have no page in it, + $noPag = 0; + $subjects = $xpaths->xpath("/org.olat.course.Structure//*[ident='" . $id . "']/children/*[type = 'st' or type = 'sp' or type = 'bc' or type = 'en' or type = 'iqtest' or type = 'iqself' or type = 'iqsurv']"); + if ($subjects != null) { + foreach ($subjects as $item) { + + switch ($item->type) { + case "iqtest": + case "iqself": + case "iqsurv": + $noPag ++; + $subjectObject = new Subject; + break; + + case "en": + $noPag ++; + $subjectLearningObject = $xpaths->xpath("//*[ident = '$item->ident']/learningObjectives"); + + foreach ($subjectLearningObject as $subjecLearningObjects) { + $learningObjectives = $subjecLearningObjects; + } + + + $subjectObject = new SubjectLearningObjectives((string) $learningObjectives); + break; + + case "bc": + $noPag ++; + $subjectObject = new SubjectDropFolder(); + $course_map = getDirectoryList("$pathCourse" . "export/$item->ident"); + for ($i = 0; count($course_map) > $i; $i++) { + $location = "$pathCourse" . "export/$item->ident/$course_map[$i]"; + + $FolderObject = new Folder( + (string) $course_map[$i], + (string) $location, + (string) filesize($location), + (string) filetype($location), + (string) date("F d Y H:i:s.", filemtime($location))); + $subjectObject->setSubjectFolders($FolderObject); + } + + + break; + + case "sp": + case "st": + $subjectPagePath = $xpaths->xpath("//*[ident = '$item->ident']/moduleConfiguration/config//string[starts-with(.,'/')]"); + + foreach ($subjectPagePath as $subjecPage) { + $htmlPage = $subjecPage; + $noPag ++; + } + if (!empty($htmlPage)) { + $subjectObject = new SubjectPage((string) $htmlPage); + } + break; + } + if($noPag !=0){ + $subjectObject->setSubjectId(isset($item->ident) ? (string) $item->ident : null); + $subjectObject->setSubjectShortTitle(isset($item->shortTitle) ? (string) $item->shortTitle : null); + $subjectObject->setSubjectType(isset($item->type) ? (string) $item->type : null); + +// // *********************** dit is de recursie !!!! ****************************** + getSubjects($subjectObject, $item->ident, $xpaths, $pathCourse); + $object->setSubject($subjectObject); + }} + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Extra functions +// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +function getDirectoryList($directory) { +// create an array to hold directory list + $results = array(); + if (file_exists($directory)) { + + +// create a handler for the directory + $handler = opendir($directory); + +// open directory and walk through the filenames + + while ($file = readdir($handler)) { +// if file isn't this directory or its parent, add it to the results + if ($file != "." && $file != "..") { + $results[] = $file; + } + } +// tidy up: close the handler + closedir($handler); + } +// done! + return $results; +} + +function format_bytes($a_bytes) { + if ($a_bytes < 1024) { + return $a_bytes . ' B'; + } + elseif ($a_bytes < 1048576) { + return round($a_bytes / 1024, 2) . ' KB'; + } + elseif ($a_bytes < 1073741824) { + return round($a_bytes / 1048576, 2) . ' MB'; + } + elseif ($a_bytes < 1099511627776) { + return round($a_bytes / 1073741824, 2) . ' GB'; + } + elseif ($a_bytes < 1125899906842624) { + return round($a_bytes / 1099511627776, 2) . ' TB'; + } + elseif ($a_bytes < 1152921504606846976) { + return round($a_bytes / 1125899906842624, 2) . ' PB'; + } + elseif ($a_bytes < 1180591620717411303424) { + return round($a_bytes / 1152921504606846976, 2) . ' EB'; + } + elseif ($a_bytes < 1208925819614629174706176) { + return round($a_bytes / 1180591620717411303424, 2) . ' ZB'; + } + else { + return round($a_bytes / 1208925819614629174706176, 2) . ' YB'; + } +} + +?> diff --git a/qtici_course/qtici_course.info b/qtici_course/qtici_course.info new file mode 100644 index 0000000000000000000000000000000000000000..bd0cda0283621f3815351bd8a2e991810c4ed891 --- /dev/null +++ b/qtici_course/qtici_course.info @@ -0,0 +1,20 @@ +name = QTI Course Import +description = This module imports QTI 1.2 courses and tests, and enables extra functionality. +core = 7.x +package = QTICI + +files[] = qtici_course.install + +files[] = classes/Chapter.php +files[] = classes/ChapterDropFolder.php +files[] = classes/ChapterLearningObject.php +files[] = classes/ChapterPage.php +files[] = classes/Course.php +files[] = classes/Folder.php +files[] = classes/Subject.php +files[] = classes/SubjectDropFolder.php +files[] = classes/SubjectLearningObject.php +files[] = classes/SubjectPage.php +files[] = classes/SubjectTest.php + +dependencies[] = qtici diff --git a/qtici_course/qtici_course.install b/qtici_course/qtici_course.install new file mode 100644 index 0000000000000000000000000000000000000000..e894ae88fe540545092f06b7dd881a9703063889 --- /dev/null +++ b/qtici_course/qtici_course.install @@ -0,0 +1,396 @@ +<?php + +/* + * @file + * Install file of Dutch++ + * + * Makes the database with 15 tables which can be split up into two schemas. + * One for courses and the other for the exercices. + */ + +/** + * Implements hook_schema(). + */ +function qtici_course_schema() { + + $schema['qtici_course_general_info'] = array( + 'description' => t('Stores information about the course, chapter or Subject.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t("Primary key: A unique ID for each general_info."), + ), + 'general_id' => array( + 'type' => 'int', + 'size' => 'big', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t('This key refers to the general id of the course, chapter or subject.'), + ), + 'short_title' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => t("Each course, chapter or subject contains a short title."), + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 6, + 'not null' => TRUE, + 'description' => t('Each course, chapter or subject contains a type.'), + ), + 'publish_status' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => FALSE, + 'description' => t('Each course, chapter or subject contains a type.'), + ), + ), + 'indexes' => array( + 'general_id' => array('general_id'), + ), + 'primary key' => array('id'), + ); + + $schema['qtici_course_folder'] = array( + 'description' => t('Stores information about the folder.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t("Primary key: A unique ID for each folder."), + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'description' => t('This key refers to the name of the folder.'), + ), + 'location' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'description' => t('This key refers to the location of the folder.'), + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'description' => t('This key refers to the type of the folder.'), + ), + 'size' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'description' => t('This key refers to the size of the folder.'), + ), + 'modified' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'description' => t('This key refers to the last modified date of the folder.'), + ), + ), + 'primary key' => array('id'), + ) + ; + + /** + * Implements table Subject. + */ + $schema['qtici_course_page'] = array( + 'description' => t('Stores information about the page.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t("Primary key: A unique ID for each page."), + ), + 'location' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'description' => t('This key refers to the path of the page.'), + ), + 'function_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t(''), + ), + ), + 'indexes' => array( + 'function_id' => array('function_id'), + ), + 'primary key' => array('id'), + ) + ; + /** + * Implements table Subject. + */ + $schema['qtici_course_learning_object'] = array( + 'description' => t('Stores information about the learning object.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t("Primary key: A unique ID for each learning object."), + ), + 'learning_object' => array( + 'type' => 'text', + 'size' => 'normal', + 'not null' => FALSE, + 'description' => t('This key refers to the learning object.'), + ), + 'function_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t(''), + ), + ), + 'indexes' => array( + 'function_id' => array('function_id'), + ), + 'primary key' => array('id'), + ) + ; + + + /** + * Implements table drop_folder. + */ + $schema['qtici_course_drop_folder'] = array( + 'description' => t('Stores information about the drop folder.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t("Primary key: A unique ID for each drop folder."), + ), + 'function_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t(''), + ), + 'folder_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t(''), + ), + ), + 'indexes' => array( + 'function_id' => array('function_id'), + 'folder_id' => array('folder_id'), + ), + 'primary key' => array('id'), + ); + + $schema['qtici_course_function'] = array( + 'description' => t('Stores information about the function.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t("Primary key: A unique ID for each function."), + ), + 'chapter_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'description' => t('This key refers to a page.'), + ), + 'subject_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'description' => t('This key refers to a learning object.'), + ), + ), + 'indexes' => array( + 'subject_id' => array('subject_id'), + 'chapter_id' => array('chapter_id'), + ), + 'primary key' => array('id'), + ); + + /** + * Implements table course. + */ + $schema['qtici_course'] = array( + 'description' => t('Stores information about the course.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t("Primary key: A unique ID for each course."), + ), + 'long_title' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'description' => t("Each course contains a long title."), + ), + 'version' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'description' => t('Each course contains a version-nr.'), + ), + 'general_info_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t('This key refers to the general info of the course.'), + ), + 'status' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t('This key refers to the status of the course.'), + ), + 'filepath' => array( + 'type' => 'text', + 'size' => 'normal', + 'not null' => FALSE, + 'description' => t('Refers to the file path with respect to sites/default/files/qtici/'), + ), + 'date' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'description' => t('Contains the date when a course is uploaded.'), + ), + ), + 'indexes' => array( + 'general_info_id' => array('general_info_id'), + ), + 'primary key' => array('id'), + ); + + /** + * Implements table chapter. + */ + $schema['qtici_course_chapter'] = array( + 'description' => t('Stores information about the chapter.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t("Primary key: A unique ID for each chapter."), + ), + 'visibility_begin_date' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => FALSE, + 'description' => t("Each chapter contains a visibility begin date."), + ), + 'visibility_end_date' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => FALSE, + 'description' => t("Each chapter contains a visibility end date."), + ), + 'access_begin_date' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => FALSE, + 'description' => t("Each chapter contains a access begin date."), + ), + 'access_end_date' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => FALSE, + 'description' => t("Each chapter contains a access end date."), + ), + 'general_info_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t('This key refers to the general info of the chapter.'), + ), + 'course_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t('This key refers to the course where it is part of.'), + ), + 'status' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t('This key refers to the status of the course.'), + ), + ), + 'indexes' => array( + 'general_info_id' => array('general_info_id'), + 'course_id' => array('course_id'), + ), + 'primary key' => array('id'), + ); + + /** + * Implements table Subject. + */ + $schema['qtici_course_subject'] = array( + 'description' => t('Stores information about the subject.'), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'size' => 'normal', + 'not null' => TRUE, + 'description' => t("Primary key: A unique ID for each subject."), + ), + 'general_info_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t('This key refers to the general info of the subject.'), + ), + 'chapter_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'description' => t('This key refers to the chapter where it is part of.'), + ), + 'subject_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'description' => t('This key refers to the subject where it is part of.'), + ), + 'status' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => t('This key refers to the status of the course.'), + ), + ), + 'indexes' => array( + 'general_info_id' => array('general_info_id'), + 'chapter_id' => array('chapter_id'), + 'subject_id' => array('subject_id'), + ), + 'primary key' => array('id'), + ); +} \ No newline at end of file diff --git a/qtici_course/qtici_course.module b/qtici_course/qtici_course.module new file mode 100644 index 0000000000000000000000000000000000000000..9de77d31e88293e60eb0fb408e8289a9d9348a54 --- /dev/null +++ b/qtici_course/qtici_course.module @@ -0,0 +1,271 @@ +<?php + +/* + * @file + * Module file of Dutch++. Only hooks, access, blocks and menus functions. + */ + +module_load_include('inc', 'qtici_course', 'forms'); +module_load_include('inc', 'qtici_course', 'functions'); +module_load_include('php', 'qtici_course', 'object_course_parser'); +module_load_include('inc', 'qtici_course', 'views'); + +/** + * Implements hook_menu(). + * + * Here we set up the URLs (menu entries) for the + * form examples. Note that most of the menu items + * have page callbacks and page arguments set, with + * page arguments set to be functions in external files. + */ +function qtici_course_menu() { + $items['overview_courses'] = array( + 'title' => 'Cursussen', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_cursus_overview'), + 'access callback' => 'access_teacher_student', + 'access arguments' => array(1), + 'description' => 'Dit geeft je al de cursussen weer', + 'menu_name' => 'main-menu', + 'type' => MENU_NORMAL_ITEM, + 'weight' => 1, + ); + $items['overview_courses/overview'] = array( + 'title' => 'Cursussen', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_cursus_overview'), + 'access callback' => 'access_teacher_student', + 'access arguments' => array(1), + 'description' => 'Dit geeft je al de cursussen weer', + 'menu_name' => 'menu-course-administration', + 'type' => MENU_NORMAL_ITEM, + 'weight' => 1, + ); + $items['overview_courses/course'] = array( + 'title' => 'Chapter', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('show_course'), + 'access callback' => 'access_teacher_student', + 'access arguments' => array(1), + 'description' => 'Dit geeft je al de chapters weer.', + 'type' => MENU_NORMAL_ITEM, + 'weight' => 3, + 'menu_name' => 'qtici_course', + ); + $items['overview_courses/page'] = array( + 'page callback' => 'qtici_course_page_callback', + 'access callback' => 'access_teacher_student', + 'access arguments' => array(1), + 'type' => MENU_CALLBACK, + ); + $items['overview_courses/conf'] = array( + 'page callback' => 'course_conf_page_callback', + 'access arguments' => array('teacher'), + 'type' => MENU_CALLBACK, + ); + $items['overview_courses/courseConf'] = array( + 'title' => 'Configuratie', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('qtici_conf'), + 'access arguments' => array('teacher'), + 'description' => 'Dit laat je toe om een cursus te configureren.', + 'type' => MENU_NORMAL_ITEM, + 'weight' => 8, + 'menu_name' => 'qtici_course', + ); + + return $items; +} + +/** + * Implements hook_block_info(). + * + * This hook declares what blocks are provided by the module. + */ +function qtici_course_block_info() { + $blocks['qtici_course'] = array( + 'info' => t('Dit toont alle blocken weer'), + 'region' => 'sidebar_first', + 'weight' => 1, + 'status' => 1, + 'visibility' => '1', + 'pages' => "overview_courses/course", + 'cache' => 'DRUPAL_NO_CACHE', + ); + + return $blocks; +} + +/** + * Implements hook_block_view(). + * + * This hook generates the contents of the blocks themselves. + */ + +/** + * This function will show you the dynamic treeview in sidebar first and will trigger the recursive function "recursive" for adding the list items + * into the 'content' of the block + */ +function qtici_block_view($delta = '') { + $block = array(); + $courseID = ''; + if (isset($_GET['page'])) { + $page_rhtml = $_GET['page']; + } + if (isset($_GET['id'])) { + $id = $_GET['id']; + $courseID = $_GET['id']; + + $query = db_select('qtici_course', 'c'); + $query->join('qtici_course_general_info', 'g', 'c.general_info_id = g.id'); //JOIN node with users + $query + ->fields('c', array('id')) + ->fields('g', array('short_title', 'general_id')) + ->condition('c.id', $id, '=') + ; + $result = $query->execute(); + if ($result->rowCount() == 0) { + $id = 0; + } + } + else { + $id = 'empty'; + }; + if (count($id) < 1) { + $id = 'empty'; + } + switch ($delta) { + case 'qtici_course': + if ($id == 'empty') { + $id = "mislukt"; + $block['subject'] = "$id"; + $block['content'] = t('Jammer maar helaas'); + break; + } + else { + drupal_add_css(drupal_get_path('module', 'qtici') . '/css/treeView.css'); + drupal_add_css(drupal_get_path('module', 'qtici') . '/css/images.css'); + + //for html-pages: video + drupal_add_js(drupal_get_path('module', 'qtici') . '/js/flowplayer/flowplayer-3.2.11.min.js'); + + $query = db_select('qtici_course', 'c'); + $query->join('qtici_course_general_info', 'g', 'c.general_info_id = g.id'); //JOIN node with users + $query + ->fields('c', array('id')) + ->fields('g', array('short_title', 'general_id')) + ->condition('c.id', $id, '=') + ; + $cours_info = $query->execute(); + $query = null; + $query = db_select('qtici_course_chapter', 'ch'); + $query + ->join('qtici_course_general_info', 'g', 'ch.general_info_id = g.id'); //JOIN node with users + $query + ->fields('ch', array('id')) + ->fields('g', array('short_title', 'general_id')) +// ->orderBy('short_title', 'DESC')//ORDER BY created + ->condition('ch.course_id', $id, '=') + ; + $result = $query->execute(); + +// Create your block content here + $path = drupal_get_path('module', 'qtici'); + $int = 0; + foreach ($cours_info as $item) { + if (user_access('teacher')) { + $block['subject'] = '<span style="font-size: 14px">' . t('Index of') . " $item->short_title <a href='javascript:getNodeId()' class='img_past'></a></span>"; + } + else { + $block['subject'] = '<span style="font-size: 14px">' . t('Index of') . " $item->short_title" . '</span>'; + } + } + $block['content'] = ""; + $block['content'] .= '<script type="text/javascript" src="http://static.jstree.com/v.1.0pre/jquery.jstree.js"></script>'; + $block['content'] .= " "; + + $block['content'] .= '<div id="treeview" class="demo" style="height: 350px; width: 210px; margin-left: -20px; border-width: 0px;background-color:#F6F6F2">'; + $block['content'] .= '<ul>'; + $i = 0; + $html = ""; + $blockArray = recursive($id, NULL, NULL, $i); + $html = $blockArray[0]; + $block['content'] .= $html; + $block['content'] .= '</ul>'; + $block['content'] .= '</div>'; + + drupal_add_js(drupal_get_path('module', 'qtici') . '/js/jquery.mCustomScrollbar.js'); + + if (!isset($page_rhtml)) { + + $block['content'] .= + $block['content'] .= ' <script type="text/javascript" class="source below"> + function getNodeId () { + var jstreeNodeSelected = $("#treeview").jstree("get_selected").children().next().attr("id"); + jstreeNodeSelected = jstreeNodeSelected.replace("node_id_",""); + + var url = document.location.href.substring(0, document.location.href.indexOf("&id=")); + window.prompt("Kopieer naar klembord: Ctrl+C, Enter", url + "&id=' . $courseID . '&page=" + jstreeNodeSelected); + + } + $(function () { + $("#treeview") + .jstree({ "ui" : { + "initially_select" : [ "rhtml_1" ] + },"core" : { "initially_open" : [ "rhtml_1" ] }, + "plugins" : ["themes","html_data","ui","crrm","hotkeys"] }) + + // 1) the loaded event fires as soon as data is parsed and inserted + .bind("select_node.jstree", function (event, data) { + // `data.rslt.obj` is the jquery extended node that was clicked + id = data.rslt.obj.attr("id") + var openPage = document.getElementById(id).childNodes[1].getAttribute("href"); + eval(openPage); + }) + // 2) but if you are using the cookie plugin or the core `initially_open` option: + .one("reopen.jstree", function (event, data) { }) + // 3) but if you are using the cookie plugin or the UI `initially_select` option: + .one("reselect.jstree", function (event, data) { }); + }); + </script>'; + } + else { + $block['content'] .= ' <script type="text/javascript" class="source below"> + function getNodeId () { + var jstreeNodeSelected = $("#treeview").jstree("get_selected").children().next().attr("id"); + jstreeNodeSelected = jstreeNodeSelected.replace("node_id_",""); + + var url = document.location.href.substring(0, document.location.href.indexOf("&id=")); + window.prompt("Kopieer naar klembord: Ctrl+C, Enter", url + "&id=' . $courseID . '&page=" + jstreeNodeSelected); + + } + $(function () { + if ("' . $page_rhtml . '" != "rhtml_1") { + var page_rhtml = $("#node_id_' . $page_rhtml . '").parent().attr("id"); + } + + $("#treeview") + .jstree({ "ui" : { + "initially_select" : [ page_rhtml ] + },"core" : { "initially_open" : [ page_rhtml ] }, + "plugins" : ["themes","html_data","ui","crrm","hotkeys"] }) + + // 1) the loaded event fires as soon as data is parsed and inserted + .bind("select_node.jstree", function (event, data) { + // `data.rslt.obj` is the jquery extended node that was clicked + id = data.rslt.obj.attr("id") + var openPage = document.getElementById(id).childNodes[1].getAttribute("href"); + eval(openPage); + }) + // 2) but if you are using the cookie plugin or the core `initially_open` option: + .one("reopen.jstree", function (event, data) { }) + // 3) but if you are using the cookie plugin or the UI `initially_select` option: + .one("reselect.jstree", function (event, data) { }); + }); + </script>'; + } + break; + } + } + return $block; +} diff --git a/qtici_course/views.inc b/qtici_course/views.inc new file mode 100644 index 0000000000000000000000000000000000000000..7e6c2492609df881227840381bb3424f5fe3bce6 --- /dev/null +++ b/qtici_course/views.inc @@ -0,0 +1,839 @@ +<?php + +/** + * @file - All functions that return HTML strings go here + */ + +/** + * This function will show you an overview of all the coursus + */ +function cursus_overview() { + $path = drupal_get_path('module', 'qtici'); + + drupal_add_library('qtici', 'demopage'); + drupal_add_library('qtici', 'demotable'); + drupal_add_library('qtici', 'images'); + +// drupal_add_js(drupal_get_path('module', 'qtici') . '/js/jquery.js'); +// drupal_add_js(drupal_get_path('module', 'qtici') . '/js/jquery.dataTables.js'); +// drupal_add_css(drupal_get_path('module', 'qtici') . '/css/images.css'); +// drupal_add_css(drupal_get_path('module', 'qtici') . '/css/demo_page.css'); +// drupal_add_css(drupal_get_path('module', 'qtici') . '/css/demo_table.css'); + /** + * By using ajax, the whole page would not refresh, only a particular part of the page, which will give a more pleasure feeling + * The ajax will trigger course_conf_page_callback + */ +//$html = '<script type="text/javascript" charset="utf-8">$(document).ready(function() {oTable = $(\'#example\').dataTable({"bJQueryUI": true,"sPaginationType": "full_numbers"});} );'; + $html = '<script type="text/javascript" charset="utf-8">'; + if (user_access('teacher')) { + $html .= " + + var lastChecked = null; + $(document).ready(function() { + var chkboxes = $('.selectInputArray'); + chkboxes.click(function(event) { + if (!lastChecked) { + lastChecked = this; + return; + } + + if (event.shiftKey) { + var start = chkboxes.index(this); + var end = chkboxes.index(lastChecked); + chkboxes.slice(Math.min(start, end), Math.max(start, end) + 1).attr('checked', lastChecked.checked); + } + + lastChecked = this; + }); + }); + function loadPage (id, action, extra) { + var deleteTest; + $.ajax({ + beforeSend: function () { + + if (action == 'delete_course'){ + var answer = confirm('" . t('Ben je zeker dat je deze records wilt verwijderen?') . "') + if (answer == false){ + return false; + } + + deleteTest = confirm('" . t('Wilt u ook de bijhorende oefeningen verwijderen?') . "') + + $(\"#loading\").show(); + } + }, + success: function () { + if (action == '(un)publish_course'){ + $('#inhoud').load(\"?q=overview_courses/conf\", { 'var': [id,action,extra]}) + } else{ + var ammountOfCheckboxes = extra; + var i = 0; + var position = 0; + var checkedCourses = new Array(); + for (i = 0; i < ammountOfCheckboxes; i++){ + if ($(\"#selectCourseArray\"+i).length > 0) { + if (document.getElementById(\"selectCourseArray\"+i).checked == true){ + checkedCourses[position] = document.getElementById(\"selectCourseArray\"+i).value + position++ + } + } + } + if (checkedCourses.length > 0){ + + $('#inhoud').load(\"?q=overview_courses/conf\", { 'var': [checkedCourses,action,extra,deleteTest]}) + } else{alert('" . t('U heeft niets geselecteerd') . "')} + }} + }); + $('#inhoud').ajaxStop(function(){ + $(\"#loading\").hide(); + if (action == 'delete_course') { + window.location.reload(true); + } + }); + } + "; + } + $html .= '</script>'; +//$html = "<script>document.write('<style>body{padding : 65px;}</style>');</script>"; + $html .= '<div id="loading" style="display:none;"><p class="img_loading">' . t('Even geduld...') . '</p></div>'; + $html .= '<script type="text/javascript"> $(window).load(function(){$(".example255").dataTable(); });</script>'; + $html .= '<div id="demo">'; + $html .= '<table cellpadding="0" cellspacing="0" border="0" class="example255">'; + + + $html .= '<thead>'; + $html .= '<tr>'; + if (user_access('teacher')) { + $html .= '<th></th>'; + } + $html .= '<th style="color:#555;">' . t('Titel') . '</th>'; + $html .= '<th style="color:#555;">' . t('Omschrijving') . '</th>'; + $html .= '<th style="color:#555;">' . t('Datum') . '</th>'; + if (user_access('teacher')) { + $html .= '<th style="color:#555;">' . t('Opties') . '</th>'; + } + $html .= '</tr>'; + $html .= '</thead>'; + $html .= '<tbody>'; + $result = null; + if (user_access('teacher')) { + $query = db_select('qtici_course', 'c'); + $query->join('qtici_course_general_info', 'g', 'c.general_info_id = g.id'); //JOIN node with users + $query + ->fields('c', array('id', 'status', 'long_title', 'date')) + ->fields('g', array('short_title', 'general_id', 'publish_status')) + ->orderBy('short_title')//ORDER BY created + ; + $result = $query->execute(); + } else if (user_access('student')) { + $query = db_select('qtici_course', 'c'); + $query->join('qtici_course_general_info', 'g', 'c.general_info_id = g.id'); //JOIN node with users + $query + ->fields('c', array('id', 'status', 'long_title', 'date')) + ->fields('g', array('short_title', 'general_id', 'publish_status')) + ->condition('g.publish_status', '1', '=') + ->orderBy('short_title'); + $result = $query->execute(); + } + + + $i = 0; + foreach ($result as $item) { + $html .= "<tr id=\"course_table_row_$i\" class=\"gradeC\">"; + if (user_access('teacher')) { + $html .= "<td style=\"width:10px\"><input id=\"selectCourseArray$i\" class=\"selectInputArray\" type=\"checkbox\" value=\"$item->id-$i\" /></td>"; + } + if ($item->publish_status == 0) { + $html .= "<td><a href=\"?q=overview_courses/course&id=$item->id\">$item->short_title</a></td>"; + if (user_access('teacher')) { + $html .= "<td><a href='javascript:window.alert(\"Deze cursus is nog niet actief\");'>$item->long_title</a></td>"; + } else { + $html .= "<td>$item->long_title</td>"; + } + } else { + $html .= "<td><a href=\"?q=overview_courses/course&id=$item->id\">$item->short_title</a></td>"; + if (user_access('teacher')) { + $html .= "<td><a href=\"?q=overview_courses/courseConf&id=$item->id\">$item->long_title</a></td>"; + } else { + $html .= "<td>$item->long_title</td>"; + } + } + $html .= "<td>$item->date</td>"; + if (user_access('teacher')) { + if ($item->publish_status == 0) { + $imagePublished = 'img_unpublished'; + } else { + if ($item->status == 0) { + $imagePublished = 'img_published'; + } else { + $imagePublished = 'img_halfpublished'; + } + } + $html .= "<td style=\"width:55px\" class=\"center\"> + <a href=\"?q=overview_courses/courseConf&id=$item->id\" class=\"img_display\"></a> + <a href='' onclick=\"window.prompt('Kopieer naar klembord: Ctrl+C, Enter', document.location.href + '/course&id=' + $item->id);\" class='img_past'></a> + <a id='img_publ_$i' href=\"javascript:loadPage('$item->id','(un)publish_course','$i')\" class=\"$imagePublished\"></a> + <span style='display:none;'>" . (string) $item->publish_status . "</span> + </td>"; + } + $html .= '</tr>'; + $i++; + } + $html .= '</tbody></table>'; + if (user_access('teacher')) { + $amount_of_checkboxen = $i; + $html .= "<br/><br/> + <a class=\"form-submit\" href=\"javascript:loadPage('(un)publish','checkbox_course','$amount_of_checkboxen')\">" . t('(Un)publish') . "</a> + <a class=\"form-submit\" href=\"javascript:loadPage('delete','delete_course','$amount_of_checkboxen')\">" . t('Delete') . "</a> + + <div id='inhoud'></div>"; + } + $html .= "</div>"; + + return $html; +} + +/** + * This function will show you the page, learning object or dropfolder, depending from the selected note in the treeveiw + */ +function qtici_course_page_callback() { + $course_info = $_POST["var"]; + $id = $course_info[0]; + $lvl = $course_info[1]; + $courseID = $course_info[2]; + global $base_url; + + $course_location = NULL; + $query = db_select('qtici_course', 'c'); + $query + ->fields('c', array('id', 'filepath')) + ->condition('c.id', $courseID, '=') + ; + $result = $query->execute(); + + foreach ($result as $item) { + $course_location = 'sites/default/files/qtici/' . $item->filepath; + } + + $start_id = $id; + if ($lvl == 1) { + $query = db_select('qtici_course_function', 'f'); + $query + ->fields('f', array('id')) + ->condition('f.chapter_id', $id, '=') + ; + } else { + $query = db_select('qtici_course_function', 'f'); + $query + ->fields('f', array('id')) + ->condition('f.subject_id', $id, '=') + ; + } + $result = $query->execute(); + foreach ($result as $res) { + $id = $res->id; + } + $query = db_select('qtici_course_learning_object', 'l'); + $query + ->fields('l', array('learning_object')) + ->condition('l.function_id', $id, '=') + ; + $result = $query->execute(); + + if ($result->rowCount() > 0) { + foreach ($result as $res) { + echo "$res->learning_object"; + } + } else { + + $query = db_select('qtici_course_page', 'p'); + $query + ->fields('p', array('location')) + ->condition('p.function_id', $id, '=') + ; + $result = $query->execute(); + if ($result->rowCount() > 0) { + foreach ($result as $res) { + $location = "$res->location"; + } + $pathToHTMLPages = $course_location . '/coursefolder'; + $page = file_get_contents($pathToHTMLPages . $location); + +//images + $changesArray = array(); + $oldChangesArray = array(); + $images = strpos_r($page, 'src="'); + foreach ($images as $image) { + if (substr($page, $image, 9) != 'src="http') { + $extractOfPage = substr($page, $image); + $positionOfLastQuote = strpos($extractOfPage, '"'); + $positionOfLastQuote = strpos($extractOfPage, '"', $positionOfLastQuote + 1); + + $content = substr($extractOfPage, 0, $positionOfLastQuote); +// $file = str_replace('src="','',$content); + $oldChangesArray[] = $content; + $changesArray[] = str_replace('src="', 'src="' . $pathToHTMLPages . '/', $content); + } + } + + for ($i = 0; $i < count($oldChangesArray); $i++) { + $page = str_replace($oldChangesArray[$i], $changesArray[$i], $page); + } +//make sure that pdf's in courses have the correct path!! + $changesArray = array(); + $oldChangesArray = array(); + $aTagsInHTML = strpos_r($page, '<a '); + foreach ($aTagsInHTML as $aTag) { //all a-tags in HTML + if (strpos(substr($page, $aTag, 100), '.pdf')) { //yes! a-tag links to a pdf-file + $postitionPDF = strpos(substr($page, $aTag), '.pdf'); //gives position of pdf + $contentElementA = substr($page, $aTag, $postitionPDF); + $oldChangesArray[] = $contentElementA; //keep track of changes + $changesArray[] = str_replace('href="', 'href="' . $pathToHTMLPages . '/', $contentElementA); + } + } + + //make changes to the page + for ($i = 0; $i < count($changesArray); $i++) { + $page = str_replace($oldChangesArray[$i], $changesArray[$i], $page); + } + + //make sure that FLASH audio will be displayed!! + global $base_url; + $changesArray = array(); + $oldChangesArray = array(); + $numberOfFlashElements = strpos_r($page, '<object'); + foreach ($numberOfFlashElements as $tag) { + $extractOfPage = substr($page, $tag); + $positionOfLastObject = strpos($extractOfPage, '</object'); + $content = substr($extractOfPage, 0, $positionOfLastObject + 9); + if (strpos($content, 'value="player.swf"') && strpos($content, 'name="flashvars"')) { //make some additional checks to ensure it would be a flash element + $file = substr($content, strpos($content, 'value="file=') + 12, (strpos($content, '3" />') + 1) - (strpos($content, 'value="file=') + 12)); + $oldChangesArray[] = $content; + + if (strpos(strtolower($file), '.mp3')) { //audio + $output = '<a href="' . $base_url . '/' . $pathToHTMLPages . '/' . $file . '" class="audio" style="display: block; width: 400px; height: 30px;"></a>'; + $output .= '<script type="text/javascript">flowplayer("a.audio", "' . $base_url . '/' . drupal_get_path('module', 'qtici') . '/js/flowplayer/flowplayer-3.2.14.swf", { + plugins: { + controls: { + fullscreen: false, + height: 30, + autoHide: false + }, + audio: { + url: "' . $base_url . '/' . drupal_get_path('module', 'qtici') . '/js/flowplayer/flowplayer.audio-3.2.10.swf" + } + }, + clip: { + autoPlay: false + }, + onLoad: function() { + this.unmute(); + } + });</script>'; + $changesArray[] = $output; + } + } + } + + //make changes to the page + for ($i = 0; $i < count($oldChangesArray); $i++) { + $page = str_replace($oldChangesArray[$i], $changesArray[$i], $page); + } + + + //make sure that video or audio will be displayed! + //but we need to know if there more video or audio elements in the page --> recurive strpos_r + global $base_url; + $changesArray = array(); + $oldChangesArray = array(); + $numberOfVideos = strpos_r($page, '<span id="olatFlashMovieViewer'); + foreach ($numberOfVideos as $tag) { + $extractOfPage = substr($page, $tag); + $positionOfLastSpan = strpos($extractOfPage, '</span>'); + $content = substr($extractOfPage, 0, $positionOfLastSpan + 7); + $file = substr($content, strpos($content, 'BPlayer.insertPlayer("') + 22, (strpos($content, '","olatFlashMovieViewer')) - (strpos($content, 'BPlayer.insertPlayer("') + 22)); + $oldChangesArray[] = $content; + + if (strpos($file, '.mp3')) { //audio + $output = '<a href="' . $base_url . '/' . $pathToHTMLPages . '/' . $file . '" class="audio" style="display: block; width: 400px; height: 30px;"></a>'; + $output .= '<script type="text/javascript">flowplayer("a.audio", "' . $base_url . '/' . drupal_get_path('module', 'qtici') . '/js/flowplayer/flowplayer-3.2.14.swf", { + plugins: { + controls: { + fullscreen: false, + height: 30, + autoHide: false + }, + audio: { + url: "' . $base_url . '/' . drupal_get_path('module', 'qtici') . '/js/flowplayer/flowplayer.audio-3.2.10.swf" + } + }, + clip: { + autoPlay: false + }, + onLoad: function() { + this.unmute(); + } + });</script>'; + $changesArray[] = $output; + } else { //video + $output = '<a href="' . $base_url . '/' . $pathToHTMLPages . '/' . $file . '" class="player" style="display: block; width: 425px; height: 300px;"></a>'; + $output .= '<script type="text/javascript">flowplayer("a.player", "' . $base_url . '/' . drupal_get_path('module', 'qtici') . '/js/flowplayer/flowplayer-3.2.14.swf", { + clip: { + autoPlay: false + }, + onLoad: function() { + this.unmute(); + } + });</script>'; + $changesArray[] = $output; + } + } + + //make changes to the page + for ($i = 0; $i < count($oldChangesArray); $i++) { + $page = str_replace($oldChangesArray[$i], $changesArray[$i], $page); + } + + //refercens to javascript + + echo $page; + } else { + $query = db_select('qtici_course_drop_folder', 'df'); + $query + ->fields('df', array('folder_id')) + ->condition('df.function_id', $id, '=') + ; + $result = $query->execute(); + if ($result->rowCount() > 0) { + foreach ($result as $res) { + $id = $res->folder_id; + } + + $query = db_select('qtici_course_folder', 'fo'); + $query + ->fields('fo', array('name', 'location', 'type', 'size', 'modified')) + ->condition('fo.id', $id, '=') + ; + $result = $query->execute(); + if ($result->rowCount() == 0) { + echo t("geen bestanden aanwezig"); + } else { + echo "<table><tr><th>" . t('Name') . "</th><th>" . t('Type') . "</th><th>" . t('Size') . "</th><th>" . t('Modified') . "</th></tr>"; + foreach ($result as $res) { + echo "<tr><td><a href='$res->location' alt='$res->name' title='Download this file'> $res->name</a></td><td>$res->type</td><td>$res->size</td><td>$res->modified</td></tr>"; + } + echo "</table>"; + } + } else { + + if ($lvl == 1) { + $query = db_select('qtici_course_chapter', 'ch'); + $query + ->join('qtici_course_general_info', 'g', 'ch.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('general_id')) + ->condition('ch.id', $start_id, '=') + ; + $result = $query->execute(); + foreach ($result as $res) { + $test = getTestIDByOLATTestID($res->general_id); + echo '<a href="?q=oefeningen/view&testid=' . $test['id'] . '" target="_blank">' . t('Klik hier om de test te starten.') . '</a>'; + } + } else { + $query = db_select('qtici_course_subject', 'sh'); + $query + ->join('qtici_course_general_info', 'g', 'sh.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('general_id')) + ->condition('sh.id', $start_id, '=') + ; + $result = $query->execute(); + foreach ($result as $res) { + $test = getTestIDByOLATTestID($res->general_id); + echo '<a href="?q=oefeningen/view&testid=' . $test['id'] . '" target="_blank">' . t('Klik hier om de test te starten.') . '</a>'; + } + } + } + } + } + exit; +} + +function course($from, $form_state) { + $path = drupal_get_path('module', 'qtici'); + $page = " + <script type='text/javascript'> + function loadPage (id, level, courseID) { + $.ajax({ + beforeSend: function () { + // here you can do things before the action start + }, + success: function () { + $('#inhoud').load(\"?q=overview_courses/page\", { 'var': [id,level,courseID]}) + } + }); + } + </script> + <div id='inhoud'></div> + "; + return $page; +} + +/** + * + * This function wil create a overview table from all the chapters and subject, you can also delete or (un)publish an chapter or subject) + * It als triggers the function recursiveConf for adding the table + */ +function conf() { + $page = ""; + if (user_access('teacher')) { + + if (isset($_GET['id'])) { + $id = $_GET['id']; + $query = db_select('qtici_course', 'c'); + $query->join('qtici_course_general_info', 'g', 'c.general_info_id = g.id'); //JOIN node with users + $query + ->fields('c', array('id')) + ->fields('g', array('short_title', 'general_id')) + ->condition('c.id', $id, '=') + ; + $result = $query->execute(); + if ($result->rowCount() == 0) { + $id = 0; + } + } else { + $id = 'empty'; + }; + if ($id > 1 || $id != 'empty') { + + foreach ($result as $res) { + + drupal_add_library('qtici', 'css'); + + // drupal_add_js(drupal_get_path('module', 'qtici') . '/js/jquery.js'); + // drupal_add_js(drupal_get_path('module', 'qtici') . '/js/jquery-ui.min.js'); + // drupal_add_js(drupal_get_path('module', 'qtici') . '/js/jquery.treeTable.js'); + // drupal_add_css(drupal_get_path('module', 'qtici') . '/css/css.css'); + $page .= " + <script type=\"text/javascript\"> + $(document).ready(function() { + $(\".treeCourse\").treeTable(); + // Make visible that a row is clicked + $(\"table#dnd-treeCourse tbody tr\").mousedown(function() { + $(\"tr.selected\").removeClass(\"selected\"); // Deselect currently selected rows + $(this).addClass(\"selected\"); + }); + // Make sure row is selected when span is clicked + $(\"table#dnd-treeCourse tbody tr span\").mousedown(function() { + $($(this).parents(\"tr\")[0]).trigger(\"mousedown\"); + }); + $(\"tr\").not(':first').hover( + function () { + $(this).css(\"background\",\"#ddd\"); + }, + function () { + $(this).css(\"background\",\"\"); + } + ); + }); + function loadPage (id, action, extra, cours_id, parents) { + $.ajax({ + beforeSend: function () { + // here you can do things before the action start + }, + success: function () { + if (action != 'checkbox'){ + $('#inhoud').load(\"?q=overview_courses/conf\", { 'var': [id,action,extra,cours_id,parents]}) + } else{ + + var ammountOfCheckboxes = $('.selectInputArray').length + + var i = 1; + var position = 0; + var checkedCourses = new Array(); + for (i = 1; i < ammountOfCheckboxes + 1; i++){ + + if (document.getElementById(\"selectCourseArray\"+i).checked == true){ + checkedCourses[position] = document.getElementById(\"selectCourseArray\"+i).value + position++ + } + } + + if (checkedCourses.length > 0){ + $('#inhoud').load(\"?q=overview_courses/conf\", { 'var': [checkedCourses,action,extra,cours_id]}) + } else{ + alert('" . t('u heeft niets geselecteerd') . "') + } + }} + }); + } + </script> + "; + $page .= "<h1 id='test'>$res->short_title</h1>"; + $page .= '<div>'; + $page .= '<table class="treeCourse" id="dnd-treeCourse"> + <thead> + <tr> + <th style="width:10px;"></th> + <th>' . t('Titel') . '</th> + <th style="width:10px; text-align:center">' . t('(Un)publish') . '</th> + </tr> + </thead> + <tbody>'; + $i = 0; + $html = ""; + $blockArray = recursiveConf($id, NULL, NULL, $i); + $html = $blockArray[0]; + $page .= $html; + $page .= '</tbody>'; + $page .= '</table>'; + $page .= '<div id="inhoud"></div></div>'; + $page .= "<br/> + <a id='img_publ_$i' class=\"form-submit\" href=\"javascript:loadPage('(un)publish','checkbox','nik','$id')\">" . t('(un)publish') . "</a>"; + } + } else { + $page .= "<h1>Oh jeejtje</h1>"; + } + } + return $page; +} + +/** + * + * this recursive function gives you a list - html - of all the chapters and subject for in the treeview + */ +function recursive($course_id = NULL, $chapter_id = NULL, $subject_id = NULL, $i = NULL, $html = "", $l = 0) { + $l++; + if ($course_id != NULL) { + + $query = db_select('qtici_course_chapter', 'ch'); + $query + ->join('qtici_course_general_info', 'g', 'ch.general_info_id = g.id'); //JOIN node with users + $query + ->fields('ch', array('id')) + ->fields('g', array('short_title', 'general_id')) + // ->orderBy('short_title', 'DESC')//ORDER BY created + ->condition('ch.course_id', $course_id, '=') + ->condition('g.publish_status', '1', '=') + ; + } else if ($chapter_id != NULL) { + + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('short_title', 'general_id')) + ->fields('su', array('id')) + ->condition('su.chapter_id', $chapter_id, '=') + ->condition('g.publish_status', '1', '='); + } else { + + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('short_title', 'general_id')) + ->fields('su', array('id')) + ->condition('su.subject_id', $subject_id, '=') + ->condition('g.publish_status', '1', '='); + } + $result = $query->execute(); + + foreach ($result as $item) { + ; + $item_id = $item->id; + $item_title = $item->short_title; + if ($course_id != NULL) { + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('short_title', 'general_id')) + ->fields('su', array('id')) + ->condition('su.chapter_id', $item_id, '=') + ->condition('g.publish_status', '1', '='); + } else { + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('short_title', 'general_id')) + ->fields('su', array('id')) + ->condition('su.subject_id', $item_id, '=') + ->condition('g.publish_status', '1', '='); + } + $amount = $query->execute(); + if ($amount->rowCount() > 0) { + $i++; + + if (isset($_GET['id'])) { + $courseID = $_GET['id']; + } + + $html .= "<li id=\"rhtml_$i\"><a id='node_id_$item_id' href=\"javascript:loadPage('$item_id','$l','$courseID')\">$item_title</a>"; + $html .= '<ul>'; + if ($course_id != NULL) { + + $htmlArray = recursive(NULL, $item_id, NULL, $i, NULL, $l); + + $html .= $htmlArray[0]; + $i = $htmlArray[1]; + } else { + + $htmlArray = recursive(NULL, NULL, $item_id, $i, NULL, $l); + $html .= $htmlArray[0]; + $i = $htmlArray[1]; + } + + $html .= '</ul>'; + $html .= "</li>"; + } else { + + if (isset($_GET['id'])) { + $courseID = $_GET['id']; + } + + $i++; + $html .= "<li id=\"rhtml_$i\"><a id='node_id_$item_id' href=\"javascript:loadPage('$item_id','$l','$courseID')\">$item_title</a>"; + $html .= "</li>"; + } + } + + $blockArray[0] = $html; + $blockArray[1] = $i; + // print_r($blockArray); + return $blockArray; +} + +/** + * + * this recursive function gives you a table - html - of all the chapters and subject for in the course config + */ +function recursiveConf($course_id = NULL, $chapter_id = NULL, $subject_id = NULL, $i = NULL, $html = "", $l = 0, $parentActive = 0, $parentId = NULL, $c_id = NULL, $parents = NULL) { + drupal_add_library('qtici', 'images'); + if (user_access('teacher')) { + $l++; + $path = drupal_get_path('module', 'qtici'); + $parentId = $i; + + if ($course_id != NULL) { + $c_id = $course_id; + $parentActive = 0; + $query = db_select('qtici_course_chapter', 'ch'); + $query + ->join('qtici_course_general_info', 'g', 'ch.general_info_id = g.id'); //JOIN node with users + $query + ->fields('ch', array('id', 'status')) + ->fields('g', array('short_title', 'general_id', 'publish_status')) + // ->orderBy('short_title', 'DESC')//ORDER BY created + ->condition('ch.course_id', $course_id, '=') + ; + } else if ($chapter_id != NULL) { + $parentActive = 1; + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('short_title', 'general_id', 'publish_status')) + ->fields('su', array('id', 'status')) + ->condition('su.chapter_id', $chapter_id, '='); + } else { + $parentActive = 1; + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('short_title', 'general_id', 'publish_status')) + ->fields('su', array('id', 'status')) + ->condition('su.subject_id', $subject_id, '='); + } + $result = $query->execute(); + if ($parents == NULL) { + $parents = $parentId; + } else { + $parents .= "-" . $parentId; + } + foreach ($result as $item) { + $item_id = $item->id; + $item_title = $item->short_title; + if ($course_id != NULL) { + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('short_title', 'general_id')) + ->fields('su', array('id', 'status')) + ->condition('su.chapter_id', $item_id, '='); + } else { + $query = db_select('qtici_course_subject', 'su'); + $query + ->join('qtici_course_general_info', 'g', 'su.general_info_id = g.id'); //JOIN node with users + $query + ->fields('g', array('short_title', 'general_id')) + ->fields('su', array('id', 'status')) + ->condition('su.subject_id', $item_id, '='); + } + $amount = $query->execute(); + if ($amount->rowCount() > 0) { + $i++; + + if ($parentActive == 1) { + $html .= "<tr id=\"node-$i\" class=\"child-of-node-$parentId\">"; + } else { + $html .= "<tr id=\"node-$i\">"; + } + if ($item->publish_status == 0) { + $imagePublished = 'img_unpublished'; + } else { + + + if ($item->status == 0) { + $imagePublished = 'img_published'; + } else { + $imagePublished = 'img_halfpublished'; + } + } + $html .= "<td><input id=\"selectCourseArray$i\" class=\"selectInputArray\" type=\"checkbox\" value=\"$item_id-$l-$i-$c_id-$parents\" /></td>"; + $html .= "<td><span class=\"folder\">$item_title</span></td>"; + $html .= "<td style=\"text-align:center\"><a id='img_publ_$i' href=\"javascript:loadPage('$item_id','$l','$i','$c_id','$parents')\" class=\"$imagePublished\"></a></td>"; + $html .= "</tr>"; + if ($course_id != NULL) { + + $htmlArray = recursiveConf(NULL, $item_id, NULL, $i, NULL, $l, $parentActive, $parentId, $c_id, $parents); + $html .= $htmlArray[0]; + $i = $htmlArray[1]; + } else { + + $htmlArray = recursiveConf(NULL, NULL, $item_id, $i, NULL, $l, $parentActive, $parentId, $c_id, $parents); + $html .= $htmlArray[0]; + $i = $htmlArray[1]; + } + } else { + $i++; + if ($parentActive == 1) { + $html .= "<tr id=\"node-$i\" class=\"child-of-node-$parentId\">"; + } else { + $html .= "<tr id=\"node-$i\">"; + } + if ($item->publish_status == 0) { + + $imagePublished = 'img_unpublished'; + } else { + $imagePublished = 'img_published'; + } + + $html .= "<td><input id=\"selectCourseArray$i\" class=\"selectInputArray\" type=\"checkbox\" value=\"$item_id-$l-$i-$c_id-$parents\" /></td>"; + $html .= "<td><span class=\"file\">$item_title</span></td>"; + $html .= "<td style=\"text-align:center\"><a id='img_publ_$i' href=\"javascript:loadPage('$item_id','$l','$i','$c_id','$parents')\" class=\"$imagePublished\"></a></td>"; + $html .= "</tr>"; + // $html .= "<li><a href=\"javascript:loadPage('')\">$item_title</a>"; + // $html .= "</li>"; + } + } + $blockArray[0] = $html; + $blockArray[1] = $i; + + // print_r($blockArray); + return $blockArray; + } +} diff --git a/qtici_test.tpl.php b/qtici_test.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..00e3b89e5ba495dbb41237f09e5f843a1e2f8749 --- /dev/null +++ b/qtici_test.tpl.php @@ -0,0 +1,51 @@ +<?php +/** + * @file - Template file for Test entity + */ + +/** + * @file + * Default theme implementation for entities. + * + * Available variables: + * - $content: An array of comment items. Use render($content) to print them all, or + * print a subset such as render($content['field_example']). Use + * hide($content['field_example']) to temporarily suppress the printing of a + * given element. + * - $title: The (sanitized) entity label. + * - $url: Direct url of the current entity if specified. + * - $page: Flag for the full page state. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. By default the following classes are available, where + * the parts enclosed by {} are replaced by the appropriate values: + * - entity-{ENTITY_TYPE} + * - {ENTITY_TYPE}-{BUNDLE} + * + * Other variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess() + * @see template_preprocess_entity() + * @see template_process() + */ +?> +<div class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>> + + <?php if (!$page): ?> + <h2<?php print $title_attributes; ?>> + <?php if ($url): ?> + <a href="<?php print $url; ?>"><?php print $title; ?></a> + <?php else: ?> + <?php print $title; ?> + <?php endif; ?> + </h2> + <?php endif; ?> + + <div class="content"<?php print $content_attributes; ?>> + <?php + print render($content); + ?> + </div> +</div> diff --git a/qtici_tests.test b/qtici_tests.test new file mode 100644 index 0000000000000000000000000000000000000000..f2dcf0a5810a664ce8db783649456fcad81c481d --- /dev/null +++ b/qtici_tests.test @@ -0,0 +1,611 @@ +<?php + +//module_load_include('test', 'simpletest_clone'); + +/** + * @file + * An example of simpletest tests to accompany the tutorial at + * http://drupal.org/node/890654. + */ + +/** + * The SimpletestExampleTestCase is a functional test case, meaning that it + * actually exercises a particular sequence of actions through the web UI. + * The majority of core test cases are done this way, but the Simpletest suite + * also provides unit tests as demonstrated in the unit test case example later + * in this file. + * + * Functional test cases are far slower to execute than unit test cases because + * they require a complete Drupal install to be done for each test. + * + * @see DrupalWebTestCase + * @see SimpletestUnitTestExampleTestCase + */ +class qticiTestCase extends DrupalWebTestCase { + + protected $privileged_user; + + public static function getInfo() { + return array( + 'name' => 'QTICI webbrowser tests', + 'description' => 'Tests of the module in a fully new environment', + 'group' => 'QTICI webbrowser tests', + ); + } + + public function setUp() { + parent::setUp('jquery_update', 'entity', 'views', 'taxonomy', 'taxonomy_entity_index', 'qtici'); // Enable any modules required for the test + // Create and log in our user. The user has the arbitrary privilege + $user = $this->drupalCreateAdminUser(); + $this->drupalLogin($user); + } + + public function drupalCreateAdminUser(array $permissions = array()) { + $roles = user_roles(); + $administratorIndex = array_search('administrator', $roles); + $teacherIndex = array_search('teacher', $roles); + $user = $this->drupalCreateUser($permissions); + $user->roles[$administratorIndex] = 'administrator'; + $user->roles[$teacherIndex] = 'teacher'; + return user_save($user); + } + + // test if the zip is uploaded and the message appears + public function testSimpleTestUploadZipSucces() { + // Create node to edit. + $edit = array(); + //select a radio button + $edit["publish"] = 1; + //test file + // $rpath = realpath('sites/default/files/qticitest-file.pdf'); + $rpath = "C:/Users/Andy/Downloads/Drupal/qti-1.zip"; + $edit["files[]"] = $rpath; + + //fill in the form and click on the upload button + $this->drupalPost('oefeningen/upload', $edit, t('Uploaden')); + //check if the result is the expected result + $this->assertText(t('De bestanden werden geüpload')); + + $testobj = new Test(); + $tests = _qtici_getAllTests(); + + //look if there are tests in the database + if (!empty($tests)) { + $result = TRUE; + } + else { + $result = FALSE; + } + + //display the message + $message = 'If there are tests in the database the value should return TRUE.'; + //check if the result is the expected result + $this->assertTrue($result, $message); + } + + //test if the error message appears if no zip is selected + public function testSimpleTestUploadZipFail() { + // Create node to edit. + $edit = array(); + //select a radio button + $edit["publish"] = 1; + + $this->drupalPost('oefeningen/upload', $edit, t('Uploaden')); + $this->assertText(t('There was a problem uploading the zip, please try again later')); + } + + //test if the error message appears if a file that is no zip is uploaded + public function testSimpleTestUploadOhterFileFail() { + + try { + // Create node to edit. + $edit = array(); + //select a radio button + $edit["publish"] = 1; + + //other file + $rpath = drupal_get_path("module", "qtici") . "/unit_test_material/unit_test_test.txt"; + + $edit["files[]"] = $rpath; + + $this->drupalPost('oefeningen/upload', $edit, t('Uploaden')); + //$this->assertText('You have to upload a .zip file'); + + $this->pass("Exception thrown for wrong input"); + } catch (Exception $ex) { + + $this->fail("No exception thrown for wrong input"); + } + } + + /** + * Detect if we're running on PIFR testbot; skip intentional failure in that + * case. It happens that on the testbot the site under test is in a directory + * named 'checkout' or 'site_under_test'. + * + * @return boolean + * TRUE if running on testbot. + */ + public function runningOnTestbot() { + return (file_exists("../checkout") || file_exists("../site_under_test")); + } + +} + +/** + * Although most core test cases are based on DrupalWebTestCase and are + * functional tests (exercising the web UI) we also have DrupalUnitTestCase, + * which executes much faster because a Drupal install does not have to be + * one. No environment is provided to a test case based on DrupalUnitTestCase; + * it must be entirely self-contained. + * + * @see DrupalUnitTestCase + */ +class qticiUnitTestCase extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'QTICI unit tests', + 'description' => 'Unit tests for the module qtici that does not use te database', + 'group' => 'QTICI unit tests', + ); + } + + function setUp() { + drupal_load('module', 'qtici'); + parent::setUp(); + } + +} + +class SimpleTestCloneTestCase extends DrupalWebTestCase { + + /** + * Tables listed in this array will not have data copied, only table + * structure. This is so that sites with large amounts of data can still be + * tested to a certain extent, without having the tests run on forever while + * the tables are copied over. + */ + protected $excludeTables = array( + 'cache', + 'cache_block', + 'cache_bootstrap', + 'cache_field', + 'cache_filter', + 'cache_form', + 'cache_image', + 'cache_menu', + 'cache_page', + 'cache_path', + 'cache_update', + 'watchdog', + ); + + /** + * Don't create test db via install, instead copy existing db. + */ + protected function setUp() { + // Copy of parent::setUp(); + global $user, $language, $conf; + + // Generate a temporary prefixed database to ensure that tests have a clean starting point. + $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000); + db_update('simpletest_test_id') + ->fields(array('last_prefix' => $this->databasePrefix)) + ->condition('test_id', $this->testId) + ->execute(); + + // Store necessary current values before switching to prefixed database. + $this->originalLanguage = $language; + $this->originalLanguageDefault = variable_get('language_default'); + $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); + $this->originalProfile = drupal_get_profile(); + $clean_url_original = variable_get('clean_url', 0); + + // Save and clean shutdown callbacks array because it static cached and + // will be changed by the test run. If we don't, then it will contain + // callbacks from both environments. So testing environment will try + // to call handlers from original environment. + $callbacks = &drupal_register_shutdown_function(); + $this->originalShutdownCallbacks = $callbacks; + $callbacks = array(); + + // Create test directory ahead of installation so fatal errors and debug + // information can be logged during installation process. + // Use temporary files directory with the same prefix as the database. + $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); + $private_files_directory = $public_files_directory . '/private'; + $temp_files_directory = $private_files_directory . '/temp'; + + // Create the directories + file_prepare_directory($public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + file_prepare_directory($private_files_directory, FILE_CREATE_DIRECTORY); + file_prepare_directory($temp_files_directory, FILE_CREATE_DIRECTORY); + $this->generatedTestFiles = FALSE; + + // Log fatal errors. + ini_set('log_errors', 1); + ini_set('error_log', $public_files_directory . '/error.log'); + + // Set the test information for use in other parts of Drupal. + $test_info = &$GLOBALS['drupal_test_info']; + $test_info['test_run_id'] = $this->databasePrefix; + $test_info['in_child_site'] = FALSE; + + // Rebuild schema based on prefixed database and such. + $schemas = drupal_get_schema(NULL, TRUE); + // Create a list of prefixed source table names. + $sources = array(); + foreach ($schemas as $name => $schema) { + $sources[$name] = Database::getConnection()->prefixTables('{' . $name . '}'); + } + + // Clone the current connection and replace the current prefix. + $connection_info = Database::getConnectionInfo('default'); + Database::renameConnection('default', 'simpletest_original_default'); + foreach ($connection_info as $target => $value) { + $connection_info[$target]['prefix'] = array( + 'default' => $value['prefix']['default'] . $this->databasePrefix, + ); + } + Database::addConnectionInfo('default', 'default', $connection_info['default']); + + // Clone each table into the new database. + foreach ($schemas as $name => $schema) { + $this->cloneTable($name, $sources[$name], $schema); + } + + // Log in with a clean $user. + $this->originalUser = $user; + drupal_save_session(FALSE); + $user = user_load(1); + + // Set up English language. + unset($GLOBALS['conf']['language_default']); + $language = language_default(); + + // Use the test mail class instead of the default mail handler class. + variable_set('mail_system', array('default-system' => 'TestingMailSystem')); + + drupal_set_time_limit($this->timeLimit); + $this->setup = TRUE; + } + + /** + * Mirror over an existing tables structure, and copy the data. + * + * @param $name + * Table name. + * @param $schema + * A Schema API definition array. + * @return + * Array of table creation results. + */ + protected function cloneTable($name, $source, $schema) { + db_create_table($name, $schema); + + $target = Database::getConnection()->prefixTables('{' . $name . '}'); + if (!in_array($name, $this->excludeTables)) { + // Explicitly name fields since the order of fields can vary + // (particularly in CCK) from that specified in the schema array. + $fields = implode(', ', array_keys($schema['fields'])); + db_query("INSERT INTO $target ($fields) SELECT $fields FROM $source"); + } + } + +} + +class qticiTestCasesDBClone extends SimpleTestCloneTestCase { + + protected $privileged_user; + + public static function getInfo() { + return array( + 'name' => 'QTICI webbrowser DB Cloned tests', + 'description' => 'Tests of the module with a copy of the real Database', + 'group' => 'QTICI webbrowser DB Cloned tests', + ); + } + + public function setUp() { + parent::setUp('jquery_update', 'entity', 'views', 'taxonomy', 'taxonomy_entity_index', 'qtici'); // Enable any modules required for the test + // Create and log in our user. The user has the arbitrary privilege + $user = $this->drupalCreateAdminUser(); + $this->drupalLogin($user); + } + + public function drupalCreateAdminUser(array $permissions = array()) { + $roles = user_roles(); + $administratorIndex = array_search('administrator', $roles); + $teacherIndex = array_search('teacher', $roles); + $user = $this->drupalCreateUser($permissions); + $user->roles[$administratorIndex] = 'administrator'; + $user->roles[$teacherIndex] = 'teacher'; + return user_save($user); + } + + //test if the error message appears if a file that is no zip is uploaded + public function testSimpleTestDeleteTest() { + + //get al the tests out of the database and fetch only the first + $tests = db_query_range('SELECT * FROM qtici_test', 0, 1, array(':f' => 0))->fetch(); + + //if there are test in the database proceed otherwise give an error message + if (!empty($tests) && count($tests) > 0) { + + //get al the section of the test out of the database + $sections = db_query('SELECT * FROM qtici_section where testid = ' . $tests->id); + + //delete the test in de virtual browser + $edit2 = array(); + $edit2["overview_table[" . $tests->id . "]"] = TRUE; + $this->drupalPost('admin/test/manage', $edit2, t('Delete')); + $this->assertText('Tests have been deleted'); + + //get the test count of test with the id of the deleted test + $query = db_select('qtici_test', 't'); + $query->fields('t'); + $query->condition('t.id', $tests->id, '='); + $test = $query->countQuery()->execute()->fetchField(); + + //look if the test count is null, when yes the test is succesfully deleted and check further if the sections and items are deleted + if ($test == 0) { + //show the succes message for the delted test + $this->pass("The test is deleted in the database"); + + //count the items in the database that belong to the deleted test + $itemsCount = 0; + foreach ($sections as $section) { + + $query = db_select('qtici_item', 'i'); + $query->fields('i'); + $query->condition('i.sectionid', $section->id, '='); + $count = $query->countQuery()->execute()->fetchField(); + + $itemsCount += $count; + } + + //count the sections that belong to the deleted test + $query = db_select('qtici_section', 's'); + $query->fields('s'); + $query->condition('s.testid', $tests->id, '='); + $sectionsCount = $query->countQuery()->execute()->fetchField(); + + //look if the count of sections is zero what means that all the sections are deleted + if ($sectionsCount == 0) { + //show message + $this->pass("The sections of the test are deleted in the database"); + } + else { + //show message + $this->fail("The sections of the test are not deleted in the database "); + } + + //look if the count of items is zero what means that all the items are deleted + if ($itemsCount == 0) { + //show message + $this->pass("The items of the test are deleted in the database"); + } + else { + //show message + $this->fail("The items of the test are not deleted in the database"); + } + } + else { + //show message + $this->fail("The test is not deleted in the database"); + } + } + else { + //show message + $this->fail("There are no tests cloned into the database!"); + } + } + + //test if the error message appears if a file that is no zip is uploaded + public function testSimpleTestUnpublishTest() { + + //get al the tests out of the database and fetch only the first + $tests = db_query_range('SELECT * FROM qtici_test', 0, 1, array(':f' => 0))->fetch(); + + //if there are test in the database proceed otherwise give an error message + if (!empty($tests) && count($tests) > 0) { + //delete the test in de virtual browser + $edit = array(); + $edit["overview_table[" . $tests->id . "]"] = TRUE; + $this->drupalPost('admin/test/manage', $edit, t('(Un)publish')); + $this->assertText('Tests have been (un)published'); + + $test = db_query_range('SELECT * FROM qtici_test where id = ' . $tests->id, 0, 1, array(':f' => 0))->fetch(); + + //look what the previous published state of the test was +// if ($tests->published == 0) { +// if ($test->published == 1) { +// $this->pass("The test is succesfully (un)published in the database" . $test->id); +// } +// else { +// $this->fail("The test is not succesfully (un)published in the database eerst " . $tests->id . " geworde " . $test->id); +// } +// } +// else { +// if ($test->published == 0) { +// $this->pass("The test is succesfully (un)published in the database"); +// } +// else { +// $this->fail("The test is no succesfully (un)published in the database"); +// } +// } + } + else { + //show message + $this->fail("There are no tests cloned into the database!"); + } + } + + //test if the error message appears if a file that is no zip is uploaded + public function testEntityTest() { + + //get al the tests out of the database and fetch only the first + $query = db_select('qtici_test', 't'); + $query->fields('t'); + $query->condition('t.title', "een mening vragen en geven", '='); + $tests = $query->execute()->fetchAll(); + $tests = $tests[0]; + + $count = 0; + $testObj = qtici_test_entity_load($tests->id); + $items = $testObj->getAllItemsFromAllSectionsInTest(); + + foreach ($items as $item) { + $count += $item->score; + } + + //if there are test in the database proceed otherwise give an error message + if (!empty($tests) && count($tests) > 0) { + //submit the test in de virtual browser + $edit = array(); + $this->drupalPost('qtici/test/' . $tests->id, $edit, t('Dien in')); + $this->assertText('Uw score is: 0 op ' . $count . '!'); + + //count the statistics that belong to the test + $query = db_select('qtici_test_statistics', 's'); + $query->fields('s'); + $query->condition('s.testid', $tests->id, '='); + $statisticsCount = $query->countQuery()->execute()->fetchField(); + + if ($statisticsCount == 0) { + //show message + $this->fail("There are no statistics saved to the database"); + } + else { + //show message + $this->pass("The statistics are saved in the database"); + } + } + else { + //show message + $this->fail("The test (een mening vragen en geven) that is needed for this unit test is not cloned into the database!"); + } + } + + //test if the error message appears if a file that is no zip is uploaded + public function testEntityTestLink() { + + //get al the tests out of the database and fetch only the first + $query = db_select('qtici_test', 't'); + $query->fields('t'); + $query->condition('t.title', "een mening vragen en geven", '='); + $tests = $query->execute()->fetchAll(); + $tests = $tests[0]; + + //if there are test in the database proceed otherwise give an error message + if (!empty($tests) && count($tests) > 0) { + //click the test link in de virtual browser + $this->drupalGet('admin/test/manage'); + $this->clickLink(t($tests->title), 0); + $this->assertText($tests->title); + } + else { + //show message + $this->fail("The test (een mening vragen en geven) that is needed for this unit test is not cloned into the database!"); + } + } + + public function testEntityItemShowAnswer() { + + //get al the tests out of the database and fetch only the first + $query = db_select('qtici_test', 't'); + $query->fields('t'); + $query->condition('t.title', "een mening vragen en geven", '='); + $tests = $query->execute()->fetchAll(); + $tests = $tests[0]; + + //if there are test in the database proceed otherwise give an error message + if (!empty($tests)) { + + $query = db_select('qtici_item', 'i'); + $query->fields('i'); + $query->join('qtici_section', 's', 's.id = i.sectionid'); + $query->join('qtici_test', 't', 't.id = s.testid'); + $query->condition('t.id', $tests->id, '='); + $items = $query->execute(); + + $itemLast = null; + foreach ($items as $item) { + if ($item->id > $itemLast->id) + $itemLast = $item; + } + + $query2 = db_select('qtici_possibility', 'p'); + $query2->fields('p'); + $query2->condition('p.itemid', $itemLast->id, '='); + $query2->condition('p.is_correct', 1, '='); + $answers = $query2->execute(); + + $answerCorrect = NULL; + foreach ($answers as $answer) { + $answerCorrect = $answer; + } + + $answerUnserialize = unserialize($answerCorrect->answer); + + $edit = array(); + $this->drupalPost('qtici/test/' . $tests->id, $edit, t('Toon antwoord')); + //$this->assertText(filter_xss($answerUnserialize["value"])); + $this->assertRaw('<div id="edit-content-' . $itemLast->id . '" class="form-wrapper">' . $answerUnserialize["value"], 'The answer is shown in the page'); + } + else { + //show message + $this->fail("The test (een mening vragen en geven) that is needed for this unit test is not cloned into the database!"); + } + } + + public function testEntityItemCheckAnswer() { + + //get al the tests out of the database and fetch only the first + $query = db_select('qtici_test', 't'); + $query->fields('t'); + $query->condition('t.title', "een mening vragen en geven", '='); + $tests = $query->execute()->fetchAll(); + $tests = $tests[0]; + + //if there are test in the database proceed otherwise give an error message + if (!empty($tests)) { + + $query = db_select('qtici_item', 'i'); + $query->fields('i'); + $query->join('qtici_section', 's', 's.id = i.sectionid'); + $query->join('qtici_test', 't', 't.id = s.testid'); + $query->condition('t.id', $tests->id, '='); + $items = $query->execute(); + + $itemLast = null; + foreach ($items as $item) { + if ($item->id > $itemLast->id) + $itemLast = $item; + } + + $query2 = db_select('qtici_possibility', 'p'); + $query2->fields('p'); + $query2->condition('p.itemid', $itemLast->id, '='); + $query2->condition('p.is_correct', 1, '='); + $answers = $query2->execute(); + + $answerCorrect = NULL; + foreach ($answers as $answer) { + $answerCorrect = $answer; + } + + $edit = array(); + $edit["item_" . $itemLast->id] = $answerCorrect->id; + $this->drupalPost('qtici/test/' . $tests->id, $edit, t('Controleer antwoord')); + //$this->assertText(filter_xss($answerUnserialize["value"])); + $this->assertRaw('<label for="edit-labelcheck-' . $itemLast->id . '">Correct, u heeft alles juist! </label>', 'The answer is correct found in source code.'); + } + else { + //show message + $this->fail("The test (een mening vragen en geven) that is needed for this unit test is not cloned into the database!"); + } + } + +} diff --git a/uploads.inc b/uploads.inc new file mode 100755 index 0000000000000000000000000000000000000000..746572443bf90a86337bf93728290327e8f21d93 --- /dev/null +++ b/uploads.inc @@ -0,0 +1,468 @@ +<?php +/** + * @file - All functions relating uploads + */ + +/** + * A form with a file upload field. + * + * This example allows the user to upload a file to Drupal which is stored + * physically and with a reference in the database. + * + * @see qtici_zip_submit() + * @ingroup qtici_zip + */ + +module_load_include('php', 'qtici', 'objectParserv3'); + +function qtici_zip($form, &$form_state) { + + if (!empty($_GET['qtici']['i'])) { + $i = $_GET['qtici']['i']; + } else { + $i = 0; + } + + if (user_access('teacher')) { + $form['#attributes'] = array('enctype' => 'multipart/form-data'); + + $form['publish'] = array( + '#type' => 'radios', + '#title' => t('Wilt u de cursus en testen publiceren?'), + '#default_value' => 1, + '#options' => array('1' => t('Ja'), '0' => t('Nee')), + ); + + $form['files'] = array( + '#type' => 'file', + '#name' => 'files[]', + '#attributes' => array('multiple' => 'multiple'), + ); + + $form['options'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ); + + global $_qtici_levels; + $form['options']['level'] = array( + '#type' => 'select', + '#options' => array(0 => t('No Level')) + $_qtici_levels, + '#required' => FALSE, + ); + + global $_qtici_topics; + $form['options']['topic'] = array( + '#type' => 'select', + '#options' => array(0 => t('No topic')) + $_qtici_topics, + '#required' => FALSE, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Uploaden'), + ); + + $_GET['qtici']['i'] = $i; + + return $form; + } +} + +/** + * Submit handler for qtici_zip(). + */ +function qtici_zip_submit($form, &$form_state) { + + //get the $_FILES in a more comfortable array! + $files = array(); + $fdata = $_FILES['files']; + if (is_array($fdata['name'])) { + for ($i = 0; $i < count($fdata['name']); ++$i) { + $files[] = array( + 'name' => $fdata['name'][$i], + 'type' => $fdata['type'][$i], + 'tmp_name' => $fdata['tmp_name'][$i], + 'error' => $fdata['error'][$i], + 'size' => $fdata['size'][$i], + ); + } + } + else { + $files[] = $fdata; + } + + $status = $form_state['values']['publish']; + $topic = $form_state['values']['topic']; + $level = $form_state['values']['level']; + + $index = 0; + foreach ($files as $file) { + $file = file_save_upload($index, array('file_validate_extensions' => array('zip'))); + $index++; + $file = file_move($file, 'public://'); + file_save($file); + + $uniqid = uniqid(); //is used to place single QTI-test in a unique directory on the server (otherwise every directory is called QTI) --> this ID will be stored in the DB qtici_test! + $zip = new ZipArchive(); + $path_file = substr($file->uri, 8); + + if ($zip->open('sites/default/files/' . $path_file) !== TRUE) { + drupal_set_message($path_file . t(' kon dit zip-bestand niet openen!'), 'error'); + } + + //upload error when the file size exceeds the maximum allowed upload size + if(file_validate_size($file, (int)(ini_get('upload_max_filesize')))){ + drupal_set_message(t('Kon dit zip-bestand uploaden wegens te groot bestand!'), 'error'); + } + + $files = array(); + $singleTest = false; + for ($i = 0; $i < $zip->numFiles; $i++) { + $files[] = $zip->getNameIndex($i); + + if ($zip->getNameIndex($i) == 'qti.xml') { + $singleTest = true; + unzipToDir('sites/default/files/' . $path_file, $uniqid . '/'); + insertZipInDatabase('sites/default/files/qtici/' . $uniqid . '/' . $zip->getNameIndex($i), $status, $topic, $level, '', $uniqid); + } + } + + if ($singleTest == false) { + $files = array(); + + $zipfiles = unzipToDir('sites/default/files/' . $path_file, str_replace('.zip', '', $file->filename) . '/'); + _qtici_save_course($zip, $file, $status, $zipfiles); + } + } + + // Set a response to the user. + drupal_set_message(t('De bestanden werden geüpload')); +} + +function qtici_zip_validate($form, &$form_state) { + + //get the $_FILES in a more comfortable array! + $files = array(); + $fdata = $_FILES['files']; + if (is_array($fdata['name'])) { + for ($i = 0; $i < count($fdata['name']); ++$i) { + $files[] = array( + 'name' => $fdata['name'][$i], + 'type' => $fdata['type'][$i], + 'tmp_name' => $fdata['tmp_name'][$i], + 'error' => $fdata['error'][$i], + 'size' => $fdata['size'][$i], + ); + } + } + else { + $files[] = $fdata; + } + + foreach ($files as $new_file) { + if ($new_file['error'] !== 0) { + form_set_error('files', t('There was a problem uploading the zip, please try again later')); + } + else { + if (!($new_file['type'] == "application/x-zip-compressed" || $new_file['type'] == "application/x-zip" || $new_file['type'] == "application/zip" || $new_file['type'] == "application/octet-stream")) { + // only zip-files + form_set_error('files', t('You have to upload a .zip file')); + } + } + } +} + +/** + * Unzip the source_file in the destination dir + * + * @param string The path to the ZIP-file. + * @param string The path where the zipfile should be unpacked, if false the directory of the zip-file is used + * @param boolean Indicates if the files will be unpacked in a directory with the name of the zip-file (true) or not (false) (only if the destination directory is set to false!) + * @param boolean Overwrite existing files (true) or not (false) + * + * @return boolean Succesful or not + */ +function unzipToDir($src_file, $dest_dir = FALSE) { + $zip = new ZipArchive; + $res = $zip->open($src_file); + $zipfiles = array(); + + if ($res === TRUE) { + if ($dest_dir === false) { + $dest_dir = substr($src_file, 0, strrpos($src_file, $splitter)) . "/"; + } + // Create the directories to the destination dir if they don't already exist + create_dirs($dest_dir); + $final_dir = 'sites/default/files/qtici/' . $dest_dir; + $files = array(); + $media = array(); + // Extract everything but .zip within the .zip + for ($i = 0; $i < $zip->numFiles; $i++) { + $filename = $zip->getNameIndex($i); + $extension = substr($filename, -4); + $repo = substr($filename, -8); + $ext = substr($extension, 1); + + if ($extension !== '.zip') { + $files[] = $filename; + } + elseif ($repo === 'repo.zip') { + $zipfiles[] = $filename; + } + } + + $zip->extractTo($final_dir, $files); + + // Extract .zips within .zip + if (!empty($zipfiles)) { + foreach ($zipfiles as $zips) { + $path_to_zip = $final_dir; + $zip->extractTo($path_to_zip, $zips); + } + } + $zip->close(); + } + else { + return false; + } + + return $zipfiles; +} + +/** + * This function creates recursive directories if it doesn't already exist + * + * @param String The path that should be created + * + * @return void + */ +function create_dirs($dir) { + $path = 'public://qtici/'; + file_prepare_directory($path, FILE_CREATE_DIRECTORY); + + if (!is_dir('sites/default/files/qtici/' . $dir)) { + $path .= $dir; + file_prepare_directory($path, FILE_CREATE_DIRECTORY); + } + else { + // Delete and create it empty again + rrmdir('sites/default/files/qtici/' . $dir); + create_dirs($dir); + } +} + + +function insertZipInDatabase($path, $published, $topic, $level, $filename = '', $folderID = '', $courseName = '') { + + $object = parseQTIToObject($path . $filename, $folderID); + $object->setOlat_testid($folderID); + $object->setPublished($published); + $object->setCourse($courseName); + $object->setDate(REQUEST_TIME); + if ($topic != 0) { + $object->setTopic($topic); + } + if ($level != 0) { + $object->setLevel($level); + } + // So far only one bundle + $object->setBundle('qtici_test'); + // Test + drupal_write_record('qtici_test', $object); + + $possibilityOption = null; + // Section + $sections = $object->getSections(); + foreach ($sections as $section) { + $section->setTestid($object->id); + drupal_write_record('qtici_section', $section); + + //Item + $items = $section->getItems(); + foreach ($items as $item) { + $item->sectionid = $section->id; + drupal_write_record('qtici_item', $item); + $possibilities = $item->getPossibilities(); + foreach ($possibilities as $possibility) { + $possibility->itemid = $item->id; + drupal_write_record('qtici_possibility', $possibility); + //watchdog(WATCHDOG_DEBUG, var_dump($possibility)); + // Replace ident with new id's for FiB + if ($item->type == 'FIB') { + $content = unserialize($item->content); + $item->content = serialize(_qtici_setTextbox($content, $possibility->ident, $possibility->id)); + } + } + drupal_write_record('qtici_item', $item, array('id')); + $feedbacks = $item->getFeedback(); + foreach ($feedbacks as $feedback) { + $feedback->itemid = $item->id; + drupal_write_record('qtici_feedback', $feedback); + } + } // END ITEM + } // END SECTION +} + +function _qtici_course_already_exists($filename) { + $filename = str_replace('.zip', '', $filename); + $result = db_query('SELECT c.id FROM qtici_course AS c WHERE c.filepath = :filename', array(':filename' => $filename)); + $num = $result->rowCount(); + + return $num; +} + +function _qtici_save_course($zip, $file, $status, $zipfiles) { + for ($i = 0; $i < $zip->numFiles; $i++) { + $files[] = $zip->getNameIndex($i); + + if (strstr($zip->getNameIndex($i), 'runstructure.xml') == "runstructure.xml") { + + $courseObject = parseCourseObject('sites/default/files/qtici/' . str_replace('.zip', '', $file->filename) . '/' . $zip->getNameIndex($i)); + $qtici_course_general_info_id = db_insert('qtici_course_general_info') + ->fields(array( + 'general_id' => $courseObject->getId(), + 'short_title' => $courseObject->getShortTitle(), + 'type' => $courseObject->getType(), + 'publish_status' => $status, + )) + ->execute(); + + $courseName = $courseObject->getShortTitle(); + + //course + $course_id = db_insert('qtici_course') + ->fields(array( + 'long_title' => $courseObject->getLongTitle(), + 'version' => $courseObject->getVersion(), + 'general_info_id' => $qtici_course_general_info_id, + 'status' => 0, + 'filepath' => str_replace('.zip', '', $file->filename), + 'date' => date("d-m-Y H:i:s", time()), + )) + ->execute(); + + foreach ($courseObject->getChapters() as $chapterObject) { + $chapter_general_info_id = db_insert('qtici_course_general_info') + ->fields(array( + 'general_id' => $chapterObject->getId(), + 'short_title' => $chapterObject->getShortTitle(), + 'type' => $chapterObject->getType(), + 'publish_status' => 1, + )) + ->execute(); + + $chapter_id = db_insert('qtici_course_chapter') + ->fields(array( + 'visibility_begin_date' => $chapterObject->getVisibilityBeginDate(), + 'visibility_end_date' => $chapterObject->getVisibilityEndDate(), + 'access_begin_date' => $chapterObject->getAccessBeginDate(), + 'access_end_date' => $chapterObject->getAccessEndDate(), + 'general_info_id' => $chapter_general_info_id, + 'course_id' => $course_id, + 'status' => 0, + )) + ->execute(); + + $chapter_function_id = db_insert('qtici_course_function') + ->fields(array( + 'chapter_id' => $chapter_id, + )) + ->execute(); + + switch ($chapterObject->getType()) { + case "iqtest": + case "iqself": + case "iqsurv": + break; + + case "en": + $chapter_learning_object_id = db_insert('qtici_course_learning_object') + ->fields(array( + 'learning_object' => $chapterObject->getChapterLearningObjectives(), + 'function_id' => $chapter_function_id, + )) + ->execute(); + break; + + case "bc": + foreach ($chapterObject->getChapterFolders() as $FolderObject) { + $chapter_folder_id = db_insert('qtici_course_folder') + ->fields(array( + 'name' => $FolderObject->getFileName(), + 'location' => $FolderObject->getFileLocation(), + 'type' => $FolderObject->getFileType(), + 'size' => $FolderObject->getFileSize(), + 'modified' => $FolderObject->getFileModified(), + )) + ->execute(); + + $chapter_drop_folder_id = db_insert('qtici_course_drop_folder') + ->fields(array( + 'function_id' => $chapter_function_id, + 'folder_id' => $chapter_folder_id, + )) + ->execute(); + } + break; + + case "sp": + case "st": + $chapter_page_id = db_insert('qtici_course_page') + ->fields(array( + 'location' => $chapterObject->getChapterPage(), + 'function_id' => $chapter_function_id, + )) + ->execute(); + break; + } + + getSubjectsDb($chapterObject->getSubjects(), $chapter_id); + } + } + } + + if (!empty($zipfiles)) { + foreach ($zipfiles as $zip) { + $path_to_zip = 'sites/default/files/qtici/' . str_replace('.zip', '', $file->filename) . '/' . $zip; + $path = str_replace('repo.zip', '', $path_to_zip); + $folderID = str_replace('/repo.zip', '', $path_to_zip); + $folderID = substr($folderID, strrpos($folderID, '/') + 1); + $qtiZip = new ZipArchive(); + $qtiZip->open($path_to_zip); + file_prepare_directory($path); + $qtiZip->extractTo($path); + for ($j = 0; $j < $qtiZip->numFiles; $j++) { + // QTI Test + if ($qtiZip->getNameIndex($j) == 'qti.xml') { + insertZipInDatabase($path . '/', $status, $qtiZip->getNameIndex($j), $folderID, $courseName); + } + } + $qtiZip->close(); + } + } +} + +function _qtici_overwrite_course($path_file, $zip, $file, $status) { + + // Delete course using the callback + $coursename = str_replace('.zip', '', $file->filename); + $result = db_query('SELECT c.id FROM qtici_course AS c WHERE c.filepath = :filename', array(':filename' => $coursename)); + $course_id = $result->fetchField(); + $courses = array( + 0 => $course_id . '-0', + ); + + $_POST["var"] = array( + 0 => $courses, + 1 => 'delete_course', + 3 => 'true', + ); + + course_conf_page_callback(); + + // Create again the course + $zipfiles = unzipToDir('sites/default/files/' . $path_file, str_replace('.zip', '', $file->filename) . '/'); + _qtici_save_course($zip, $file, $status, $zipfiles); +} diff --git a/views.inc b/views.inc new file mode 100755 index 0000000000000000000000000000000000000000..93e951b6719d80df13b1a1a60e42b25d2f68b266 --- /dev/null +++ b/views.inc @@ -0,0 +1,310 @@ +<?php + +/** + * @file - All functions that return HTML strings go here + */ +/* + * This function will collect all tests in a datatable. + * If the user is a teacher --> more permissions here + */ +function get_exercise_overview($rowsPerPageExtern, $titleExtern) { + + //Table of the exercises + //objects of the classes statistic and test for accessing their methods + $statistic = new Statistic(); + $rowsPerPage = $rowsPerPageExtern; + + //look if a title is given and when not make it blank + $title = ''; + if ($titleExtern != '') { + $title = $titleExtern; + } + + //Create a list of headers for your Html table (see Drupal 7 docs for theme_table here: http://api.drupal.org/api/drupal/includes--theme.inc/function/theme_table/7 + $header = array( + 'title' => array('data' => t('Titel'), 'field' => 'title', 'sort' => 'asc'), + 'description' => array('data' => t('Omschrijving'), 'field' => 'description', 'sort' => 'asc'), + 'course' => array('data' => t('Cursusnaam'), 'field' => 'course', 'sort' => 'asc'), + 'date' => array('data' => t('Datum'), 'field' => 'date', 'sort' => 'asc'), + 'others' => array('data' => t('Opties')), + ); + + //Create the Sql query. + $query = db_select('qtici_test', 't')->extend('PagerDefault')->limit($rowsPerPage); + $query->fields('t')->extend("TableSort")->orderByHeader($header); + + //only get the published ones if the user is a student + if (!(user_access('teacher'))) { + $query->condition('published', 1, '='); + } + + //search for the titel if given + if ($title) { + $query->condition('title', "%" . $title . "%", 'LIKE'); + } + + //execute the query + $results = $query->execute(); + + //image variables + $variables = array( + "path" => drupal_get_path('module', 'qtici') . "/css/images/detail16.png", + "attributes" => '', + ); + + //images + $image1 = theme_image($variables); + + //create rows of table + $rows = array(); + $counter = 0; + $html = ''; + $options = array(); + + //make the records of the table + foreach ($results as $node) { + + //increase counter for PastURL script + $counter++; + + //look which icons should be displayed + if (user_access('teacher')) { + + $statExists = $statistic->checkIfStatisticsExist($node->id); + if ($statExists) { + $path2 = drupal_get_path('module', 'qtici') . "/css/images/statistics.png"; + } + else { + $path2 = drupal_get_path('module', 'qtici') . "/css/images/nostatistics.png"; + } + + $variables2 = array( + "path" => $path2, + "attributes" => '', + ); + + $variables3 = array( + "path" => drupal_get_path('module', 'qtici') . "/css/images/past.png", + "attributes" => '', + ); + + if ($node->published == 1) { + $path4 = drupal_get_path('module', 'qtici') . "/css/images/published.png"; + } + else { + $path4 = drupal_get_path('module', 'qtici') . "/css/images/unpublished.png"; + } + + $variables4 = array( + "path" => $path4, + "width" => 16, + "height" => 16, + "attributes" => '', + ); + + $image2 = theme_image($variables2); + } + + //check if the value is serialized + $array = @unserialize($node->description); + if ($array === false && $node->description !== 'b:0;') { + + $options[$node->id] = array( + 'title' => l(t((string) $node->title), 'qtici/test/' . $node->id), + //l(t((string) $node->title), 'oefeningen/view', array('query' => array("testid" => $node->id))), + 'description' => $node->description, + 'course' => $node->course, + 'date' => _qtici_getNiceDate($node->date), + 'others' => t(''), + ); + } + else { + //unserialize the description so you can show the value + $descriptionUnserialized = unserialize($node->description); + + $options[$node->id] = array( + 'title' => l(t((string) $node->title), 'qtici/test/' . $node->id), + 'description' => $descriptionUnserialized["value"], + 'course' => $node->course, + 'date' => _qtici_getNiceDate($node->date), + 'others' => t(''), + ); + } + + if (user_access('teacher')) { + $options[$node->id]['others'] = l($image1, 'qtici/test/' . $node->id, array('attributes' => array('class' => 'img_display'), 'html' => TRUE)) . l($image2, 'qtici/statistics/' . $node->id, array('attributes' => array('class' => 'img_statistics'), 'html' => TRUE)); + //l($image3, 'oefeningen/copy', array('attributes' => array('class' => array('img_past'), 'html' => TRUE)) + } + } + + $args = array('quantity' => $rowsPerPage, 'tags' => array('<<', '<', '', '>', '>>')); + $html .= theme('pager', $args); + + return array('html' => $html, 'header' => $header, 'options' => $options); +} + +function get_exercise_overview_mobile($rowsPerPageExtern, $titleExtern) { + + //Table of the exercises + //objects of the classes statistic and test for accessing their methods + $rowsPerPage = $rowsPerPageExtern; + + //look if a title is given and when not make it blank + $title = ''; + if ($titleExtern != '') { + $title = $titleExtern; + } + + //Create a list of headers for your Html table (see Drupal 7 docs for theme_table here: http://api.drupal.org/api/drupal/includes--theme.inc/function/theme_table/7 + $header = array( + 'title' => array('data' => t('Titel'), 'field' => 'title', 'sort' => 'asc'), + ); + + //Create the Sql query. + $query = db_select('qtici_test', 't')->extend('PagerDefault')->limit($rowsPerPage); + $query->fields('t')->extend("TableSort")->orderByHeader($header); + + //only get the published ones if the user is a student + if (!(user_access('teacher'))) { + $query->condition('published', 1, '='); + } + + //search for the titel if given + if ($title) { + $query->condition('title', "%" . $title . "%", 'LIKE'); + } + + //execute the query + $results = $query->execute(); + + //create rows of table + $html = ''; + $options = array(); + + //make the records of the table + foreach ($results as $node) { + + $options[$node->id] = array( + 'title' => l(t((string) $node->title), 'qtici/test/' . $node->id), + ); + + } + + $args = array('quantity' => $rowsPerPage, 'tags' => array('<<', '<', '', '>', '>>')); + $html .= theme('pager', $args); + + return array('html' => $html, 'header' => $header, 'options' => $options); +} + +/** + * Display Page statistics + */ +function theme_qtici_statistics($variables) { + + $header = $variables['header']; + $rows = $variables['rows']; + $html = ''; + + if (!empty($rows)) { + $html .= '<table>'; + foreach ($header as $key => $value) { + $html .= '<tr><th style="width:175px">' . $header[$key]['data'] . '</th><td>' . $rows[0][$key] . '</td></tr>'; + } + $html .= '</table>'; + } + else { + if (!empty($variables['title'])) { + $html .= t('No statistics for !name', array('!name' => $variables['title'])); + } + } + + return $html; +} + +/** + * Display statistics for one test + */ +function _qtici_testStatistics($test) { + //Prepare table + $header = array( + 'title' => array('data' => t('Titel'), 'field' => 'title'), + 'description' => array('data' => t('Omschrijving'), 'field' => 'description'), + 'completed' => array('data' => t('Aantal'), 'field' => 'completed'), + 'time' => array('data' => t('Gem. tijd (in sec.)')), + 'score' => array('data' => t('Score'), 'field' => 'score'), + 'max_score' => array('data' => t('Max. score')), + 'date' => array('data' => t('Datum laatst gestart'), 'field' => 'date_started'), + ); + $rows = array(); + $title = ''; + + $title = l($test->title, 'qtici/test/' . $test->id); + //objects of the classes statistic and test for using their functions + $statisticObj = new Statistic(); + + $statistic = $statisticObj->getAllStatisticsByTestID($test->id); + if (!empty($statistic)) { + + $timespended = (float) $statistic['time_spended']; + $timespended /= 1000; + $timespended = round($timespended, 2); + $description = $statistic['description']; + + $rows[] = array( + 'title' => l($statistic['title'], 'qtici/test/' . $test->id), + 'description' => $description, + 'completed' => $statistic['completed'], + 'time' => $timespended, + 'score' => $statistic['score'], + 'max_score' => $test->getMaximumScoreTest(), + 'date' => _qtici_getNiceDate($statistic['date_started']), + ); + } + + $output = theme('qtici_statistics', array('header' => $header, 'rows' => $rows, 'title' => $title)); + + return $output; +} + +/** + * Display statistics for a group of tests + */ +function _qtici_generalStatistics($statistics) { + //Prepare table + $header = array( + 'numberTests' => array('data' => t('Total number of tests')), + 'published' => array('data' => t('Total number of published tests')), + 'neverDone' => array('data' => t('Tests that never have been done')), + 'durationTot' => array('data' => t('Total duration of the tests')), + 'time_spended_tot' => array('data' => t('Total time spent (in sec.)')), + 'completed' => array('data' => t('Tests completed')), + 'totalScore' => array('data' => t('Total score over total maximum score')), + 'durationAverage' => array('data' => t('Average duration of the tests')), + 'time_spended' => array('data' => t('Average spent time')), + 'scoreAverage' => array('data' => t('Average score')), + 'date_started' => array('data' => t('Date statistics started')), + ); + $rows = array(); + + $testObj = new Test(); + + // Fix times and dates + $timespended_tot = (float) $statistics['time_spended_tot']; + $timespended_tot /= 1000; + $timespended_tot = round($timespended_tot, 2); + $statistics['time_spended_tot'] = $timespended_tot; + $timespended = (float) $statistics['time_spended']; + $timespended /= 1000; + $timespended = round($timespended, 2); + $statistics['time_spended'] = $timespended; + $statistics['date_started'] = _qtici_getNiceDate($statistics['date_started']); + $statistics['totalScore'] = $statistics['totalScore'] . '/' . $statistics['max_score']; + + foreach ($header as $key => $value) { + $rows[0][$key] = $statistics[$key]; + } + + $output = theme('qtici_statistics', array('header' => $header, 'rows' => $rows)); + + return $output; +}