Warning, /education/gcompris/src/core/GCAudio.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - GCAudio.qml
0002  *
0003  * SPDX-FileCopyrightText: 2014 Johnny Jazeix <jazeix@gmail.com>
0004  *
0005  * Authors:
0006  *   Johnny Jazeix <jazeix@gmail.com>
0007  *
0008  *   SPDX-License-Identifier: GPL-3.0-or-later
0009  */
0010 import QtQuick 2.12
0011 import QtMultimedia 5.12
0012 import GCompris 1.0
0013 
0014 /**
0015  * A QML component for audio playback.
0016  * @ingroup components
0017  *
0018  * Wrapper component around QtQuick's Audio element, handling all audio
0019  * playback in GCompris uniformly.
0020  *
0021  * It maintains a queue of audio-sources (@ref files) that are played back
0022  * sequentially, to which the user can enqueue files that should be scheduled
0023  * for playback.
0024  *
0025  * To make sure an audio voice will be localized, replace the locale part
0026  * of the file by '$LOCALE'.
0027  *
0028  * To makes sure that all audio sources are normalized with respect to
0029  * ApplicationInfo.CompressedAudio replace the 'ogg' part of the file by
0030  * '$CA'.
0031  *
0032  * @inherit QtQuick.Item
0033  */
0034 Item {
0035     id: gcaudio
0036 
0037     /**
0038      * type:bool
0039      * Whether audio should be muted.
0040      */
0041     property bool muted
0042 
0043     /**
0044      * type:url
0045      * URL to the audio source to be played back.
0046      */
0047     property alias source: audio.source
0048 
0049     /**
0050      * type: positive real number less than 1
0051      * Determines intensity of the audio.
0052      */
0053     property alias volume: audio.volume
0054 
0055     /**
0056      * type:bool
0057      * Whether the audio element contains audio.
0058      */
0059     property bool hasAudio: audio.hasAudio
0060 
0061     /**
0062      * type:string
0063      * Detailed error message in case of playback errors.
0064      */
0065     property alias errorString: audio.errorString
0066     
0067     /**
0068      * type:bool
0069      * check if the player is for background music
0070      */
0071     property bool isBackgroundMusic: false
0072     
0073     /**
0074      * type:array
0075      * background music metadata
0076      */
0077     property var metaDataMusic: ["", "", "", ""]
0078     
0079     /**
0080      * Trigger this signal externally to play the next audio in the "files". This, in turn, stops the currently playing audio and check the necessary
0081      * conditions (see onStopped signal in "audio" element) and decides what needs to be done for the next audio.
0082      */
0083     signal nextAudio()
0084     onNextAudio: stop()
0085 
0086     /**
0087      * type:var
0088      * Current playback state.
0089      *
0090      * Possible values taken from Audio.status
0091      */
0092     property var playbackState: (audio.error === Audio.NoError) ?
0093                                     audio.playbackState : Audio.StoppedState;
0094 
0095     /**
0096      * type:list
0097      * Playback queue.
0098      */
0099     property var files: []
0100 
0101     /**
0102      * Emitted in case of error.
0103      */
0104     signal error
0105 
0106     /**
0107      * Emitted when playback of all scheduled audio sources has finished.
0108      */
0109     signal done
0110 
0111     //Pauses the currently playing audio
0112     function pause() {
0113         if(playbackState === Audio.PlayingState)
0114             audio.pause()
0115     }
0116 
0117     //Resumes the current audio if it had been paused
0118     function resume() {
0119         if(playbackState === Audio.PausedState || playbackState === Audio.StoppedState)
0120             audio.play()
0121     }
0122 
0123     // Helper to know if there is already an audio playing
0124     function isPlaying() {
0125         return playbackState === Audio.PlayingState;
0126     }
0127     /**
0128      * Plays back the audio resource @p file.
0129      *
0130      * @param type:string file [optional] URL to an audio source.
0131      * @returns @c true if playback has been started, @c false if file does not
0132      *          exist or audio is muted
0133      */
0134     function play(file) {
0135         if(!fileId.exists(file) || muted)
0136             return false
0137 
0138         if(file) {
0139             // Setting the source to "" on Linux fix a case where the sound is no more played if you play twice the same sound in a row
0140             source = ""
0141             source = file
0142         }
0143         if(!muted) {
0144             audio.play()
0145         }
0146         return true
0147     }
0148 
0149     /**
0150      * Stops audio playback.
0151      */
0152     function stop() {
0153         if(audio.playbackState != Audio.StoppedState)
0154             audio.stop()
0155     }
0156 
0157     /**
0158      * Schedules a @p file for audio playback.
0159      *
0160      * If there is no playback currently running, the new source will be
0161      * played back immediately. Otherwise it is appended to the file queue of
0162      * sources.
0163      *
0164      * @param type:string file File to the audio file to be played back.
0165      * @returns @c true upon success, or @c false if @p file does not exist or
0166      *             audio is muted
0167      */
0168     function append(file) {
0169         if(!fileId.exists(file) || muted)
0170             return false
0171 
0172         if(audio.playbackState !== Audio.PlayingState
0173            || audio.status === Audio.EndOfMedia
0174            || audio.status === Audio.NoMedia
0175            || audio.status === Audio.InvalidMedia) {
0176             files.push(file)
0177             silenceTimer.interval = 35
0178             silenceTimer.start()
0179         } else {
0180             files.push(file)
0181         }
0182         return true
0183     }
0184 
0185     /**
0186      * Adds a pause of the given duration in ms before playing of the next file.
0187      *
0188      * @param type:int duration_ms Pause in milliseconds.
0189      */
0190     function silence(duration_ms) {
0191         silenceTimer.interval = duration_ms
0192     }
0193 
0194     /**
0195      * Flushes the list of scheduled files.
0196      * @sa files
0197      */
0198     function clearQueue() {
0199         while(files.length > 0) {
0200             files.pop();
0201         }
0202     }
0203 
0204     /// @cond INTERNAL_DOCS
0205 
0206     function _playNextFile() {
0207         if(files.length == 0) {
0208             gcaudio.done()
0209             return
0210         }
0211 
0212         var nextFile = files.shift()
0213         if(nextFile === '') {
0214             source = ""
0215             gcaudio.done()
0216         } else {
0217             // on Ubuntu Touch, emptying the source result in an Audio error which triggers that method again endlessly
0218             if (ApplicationInfo.platform !== ApplicationInfo.UbuntuTouchOS) {
0219                source = ""
0220             }
0221             source = nextFile
0222             if(!muted)
0223                 audio.play()
0224         }
0225     }
0226 
0227     Audio {
0228         id: audio
0229         muted: gcaudio.muted
0230         onError: {
0231             // This file cannot be played, remove it from the source asap
0232             source = ""
0233             if(files.length)
0234                 silenceTimer.start()
0235             else
0236                 gcaudio.error()
0237         }
0238         onStopped: {
0239             if(files.length)
0240                 silenceTimer.start()
0241             else
0242                 gcaudio.done()
0243         }
0244         metaData.onMetaDataChanged: {
0245             if(isBackgroundMusic) {
0246                 metaDataMusic = [metaData.title, metaData.contributingArtist, metaData.year, metaData.copyright]
0247             }
0248         }
0249     }
0250 
0251     Timer {
0252         id: silenceTimer
0253         repeat: false
0254         onTriggered: {
0255             interval = 0
0256             _playNextFile()
0257         }
0258     }
0259 
0260     File {
0261         id: fileId
0262     }
0263 
0264     /// @endcond
0265 
0266 }