How to fade in and out music in MediaPlayer service - java

I am working on a mediaplayer service in the android studio and I want to fade in/out the sound when the service start/stop.
I have tried the solution from this thread :
Android Studio Mediaplayer how to fade in and out
, but it seems the code does not fit with my service. The music only plays for a split second then the music stopped.
BGMPlayer.java (Service)
public class BGMPlayer extends Service {
private MediaPlayer bgmusic1;
int volume = 0;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId){
bgmusic1 = MediaPlayer.create(this, R.raw.bgmusic1);
bgmusic1.setLooping(true);
bgmusic1.start();
FadeIn();
//we have some options for service
//start sticky means service will be explicity started and stopped
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
bgmusic1.stop();
}
private void FadeIn() {
final int FADE_DURATION = 3000; //The duration of the fade
//The amount of time between volume changes. The smaller this is, the smoother the fade
final int FADE_INTERVAL = 250;
final int MAX_VOLUME = 1; //The volume will increase from 0 to 1
int numberOfSteps = FADE_DURATION / FADE_INTERVAL; //Calculate the number of fade steps
//Calculate by how much the volume changes each step
final float deltaVolume = MAX_VOLUME / (float) numberOfSteps;
//Create a new Timer and Timer task to run the fading outside the main UI thread
final Timer timer = new Timer(true);
TimerTask timerTask = new TimerTask() {
#Override
public void run() {
fadeInStep(deltaVolume); //Do a fade step
Log.d("DEBUG","MUSIC VOLUME IS NOW " + volume);
//Cancel and Purge the Timer if the desired volume has been reached
if (volume >= 1f) {
Log.d("DEBUG","MUSIC VOLUME REACHED 1");
timer.cancel();
timer.purge();
}
}
};
timer.schedule(timerTask, FADE_INTERVAL, FADE_INTERVAL);
}
private void fadeInStep(float deltaVolume) {
bgmusic1.setVolume(volume, volume);
volume += deltaVolume;
}
}
Activity.java
#Override
protected void onStart() {
super.onStart();
//music start
startService(new Intent(this, BGMPlayer.class));
Log.d("DEBUG","LoadingScreenStart");
}
#Override
protected void onStop() {
super.onStop();
//Stop the music
stopService(new Intent(this, BGMPlayer.class));
}
DEBUG Log
04-10 21:15:29.325 6147-6147/com.example.max.curerthegame D/DEBUG: LoadingScreenStart
04-10 21:15:29.643 6147-6183/com.example.max.curerthegame D/DEBUG: MUSIC VOLUME IS NOW 0
04-10 21:15:29.893 6147-6183/com.example.max.curerthegame D/DEBUG: MUSIC VOLUME IS NOW 0
04-10 21:15:32.159 6147-6183/com.example.max.curerthegame D/DEBUG: MUSIC VOLUME IS NOW 0
04-10 21:15:32.410 6147-6183/com.example.max.curerthegame D/DEBUG: MUSIC VOLUME IS NOW 0
04-10 21:15:32.660 6147-6183/com.example.max.curerthegame D/DEBUG: MUSIC VOLUME IS NOW 0
04-10 21:15:34.672 6147-6183/com.example.max.curerthegame D/DEBUG: MUSIC VOLUME IS NOW 0
04-10 21:15:34.927 6147-6183/com.example.max.curerthegame D/DEBUG: MUSIC VOLUME IS NOW 0
04-10 21:15:49.765 6147-6183/com.example.max.curerthegame D/DEBUG: MUSIC VOLUME IS NOW 0

You could use a Runnable to repeatedly lower volume after a given time interval.
calling the function after writing it: musicVolumeF(0.2f);
MediaPlayer music;
//do this in onCreate
music = MediaPlayer.create(getApplicationContext(), R.raw.sunnybike2);
music.start();
Handler ha = new Handler();
Runnable mFadeRun;
Float musicVolumeF = 1.0f;
void musicFader(Float desiredVolume)
{
mFadeRun = new Runnable() {
#Override
public void run() {
// decrement the float value
musicVolumeF = musicVolumeF - 0.02f;
// so it does not go below 0
if (musicVolumeF < 0)
musicVolumeF = 0.0f;
// set the volume to the new slightly less float value
music.setVolume(musicVolumeF, musicVolumeF);
// so it stops if it's at about the desired volume
// you can change the 20 to make it go faster or slower
if (musicVolumeF > desiredVolume+0.02)
ha.postDelayed(mFadeRun,20);
}
};
// postDelayed re-posts the Runnable after the given milliseconds
ha.postDelayed(mFadeRun,20);
}

Related

How do I execute some code after one hour in background?

I am using a background service to play an audio whenever the user selects a button. But what I would like to do is play the audio every 60 minutes. How would I go about doing this?
I have already tried using a handler and setting the timer to 60 minute then executing the line of code that plays the audio but it just plays the audio automatically whenever the use selects the button.
public class BackgroundService extends Service {
private MediaPlayer player;
private Boolean state = false;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//i would like to play this audio every 60minute in the background
player = MediaPlayer.create(BackgroundService.this, Settings.System.DEFAULT_RINGTONE_URI);
//this will make the ringtone continuously playing
player.setLooping(true);
//staring the player
player.start();
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
//stopping the player when service is destroyed
player.stop();
}
}
It's possible with BroadcastReceiver
Set AlarmManager with your time interval
Intent intent = new Intent("Your Broadcast Receiver");
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, time_interval, pendingIntent);
Specify your BroadcastReceiver in Manifest.xml
<receiver android:name="com.package.YourReceiver">
<intent-filter>
<action android:name="Your Broadcast Receiver" />
</intent-filter>
</receiver>
try to loop your handler
final Handler handler = new Handler();
int count = 0;
final Runnable runnable = new Runnable() {
public void run() {
// do the task here
Log.d(TAG, "runn test");
if (count++ < 5)
//will continue to loop 5 times
handler.postDelayed(this, 5000);
}
};
// trigger first time
handler.post(runnable);

How to increase audio file volume

How to increase audio file volume? My app records calls, and its volume is low. Is there a way to increase the recorded call volume? Here is my code:
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setAudioSamplingRate(44100);
mediaRecorder.setAudioEncodingBitRate(AudioFormat.ENCODING_PCM_16BIT);
mediaRecorder.setAudioChannels(2);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
While using MediaPlayer you can call the setVolume(float,float) method with your object.
For eg-
MediaPlayer media = MediaPlayer.create(this,R.raw.your_file);
media.setVolume(0,0);
The value passed as parameters are on a logarithmic scale from 0 to 1.
you can use Something like this
player = MediaPlayer.create(this, R.raw.got);
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
#Override
public void run() {
player.start();
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setLooping(false);
player.setVolume(100, 100);
playAudioWithDelaysound();
stopSelf();
}
});
return super.onStartCommand(intent, flags, startId);
}
use as you needed

Activity whith pitch detect TarsosDSP no restart

I wrote a guitar tuner in music application. The main activity launch the tuner when click on button. The tuner works fine. But if I come back to main activity.
The Tuner activity start, but pitch detection does not.
The thread don't stop for the recorder.
I launch an activity Act_Accordage from the main activity with
public void clicAccordage(View v){
Intent intentAct_Accordage = new Intent(this, Act_Accordeur .class);
startActivity(intentAct_Accordage);
}
Here is my Act_Accordage activity
public final AudioDispatcher dispatcher = AudioDispatcherFactory.fromDefaultMicrophone(22050, 2048, 0);
....
dispatcher.addAudioProcessor(new PitchProcessor(PitchEstimationAlgorithm.YIN, 22050, 2048, new PitchDetectionHandler() {
#Override
public void handlePitch(PitchDetectionResult pitchDetectionResult,
AudioEvent audioEvent) {
final float pitchInHz = pitchDetectionResult.getPitch();
if (Logo.interrupted()) {
return;
}
if (pitchInHz > -1) {
runOnUiThread(new Runnable() {
#Override
public void run() {
// display name note and cursor ou UI thread
textFreqNote.setText(String.valueOf(pitchInHz) + " CREATE ");
Affcurseur(pitchInHz);
});
}
}
}));
new Thread(dispatcher, "Audio Dispatcher");
2***/I come back main with button or backbutton
public void onclicSetBackMain(View v) {
Act_Accordeur.this.finish();
Toast.makeText(Act_Accordeur.this, "Guitare accordée", Toast.LENGTH_LONG).show();}
Use dispatcher.stop(); to stop the Audio Dispatcher thread.It took me a long time in the official documentation but it was worth it.

Android Wear runnable for vibration works only in debug or with an active screen

i'm facing a big issue with android wearable development.
I've got a Runnable task that runs with an Handler every 100mS (it's purpose is to behave just like a chronograph) on my phisical wearable device (that's a Motorola 360).
When the Runnable task is alive for 20 seconds, i call a custom IntentService called VibratorService that makes the device vibrate with a defined pattern for an undefined amount of time.
When the Runnable task reaches 30 seconds alive, i dismiss the VibratorService so that the device stops vibrating while Runnable goes on and on.
For make device vibrating i decided to use an IntentService because calling directly Vibrator.vibrate() from the running task wasn't enough: Vibration stops when wearable goes in sleep mode. I should have implemented a wake lock to make it work continuously but i don't want to do it because it keeps the screen active.
During my debug sessions i've noticed that my app works good in every condition. Unfortunately doesn't works well in sleep mode when wearable is not connected in debug mode to the handheld device (that's an OnePlus) and this makes it very difficult to debug and understand where the problem is. The only way i found to make it work in this scenario is to continue moving phisically the wearable in order to keep it awake.
I can't understand if the Runnable task/Handler works properly or if there are some problem in VibratorService start or if there's an issue in Vibrator class usage when wearable is sleeping and debug is turned off. In my opinion i think is something related to the Runnable or to the Handler, but i'm not sure.
Someone of you is aware about a different behavior in debug/non debug sleep mode for Runnable/Handler/IntentService/Vibrator?
How can i make it work?
Here you are the Runnable task code:
private class TimerRunnable implements Runnable {
private boolean _isRunning;
private boolean _isPaused;
private long startTime;
private long elapsedTime;
public long getElapsedTime(){
return elapsedTime;
}
public boolean isRunning(){
return _isRunning;
}
public void resetRunnable(){
_isRunning = false;
_isPaused = false;
startTime = 0;
elapsedTime = 0;
}
public void pauseRunnable(){
_isRunning = false;
_isPaused = true;
}
public void startRunnable(){
_isRunning = true;
if(!_isPaused)
startTime = System.currentTimeMillis();
else
startTime = System.currentTimeMillis() - elapsedTime;
}
#Override
public void run() {
if(_isRunning) {
elapsedTime = System.currentTimeMillis() - startTime;
if(elapsedTime>=30000)
stopNotification();
else if(elapsedTime>=20000)
startNotification();
mHandler.postDelayed(this, REFRESH_RATE);
}
}
}
The start/stop vibration service functions:
#Override
public void startNotification() {
//Start vibration
Log.i("VibrationNotification", "Start vibrate notification service");
Intent intent = new Intent(context, VibratorService.class);
intent.addCategory(VibratorService.TAG);
intent.putExtra(VibratorService.PARAM_DELAY, delay);
intent.putExtra(VibratorService.PARAM_SLEEPDURATION, sleepDuration);
intent.putExtra(VibratorService.PARAM_VIBRATIONDURATION, vibrationDuration);
context.startService(intent);
isRunning = true;
}
#Override
public void stopNotification() {
//Stop vibration
Log.i("VibrationNotification", "Stop vibrate notification service");
Intent intent = new Intent(context, VibratorService.class);
intent.addCategory(VibratorService.TAG);
context.stopService(intent);
isRunning = false;
}
The vibrator service:
public class VibratorService extends IntentService{
public static final String TAG = "VibratorService";
public static final String PARAM_DELAY = "Delay";
public static final String PARAM_VIBRATIONDURATION = "VibrationDuration";
public static final String PARAM_SLEEPDURATION = "SleepDuration";
private boolean stopServiceRequest;
public VibratorService(){
super("VibratorService");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.i("VibrationService", "OnHandleIntent");
//Set pattern
long[] pattern = new long[]{intent.getLongExtra(PARAM_DELAY, 0),
intent.getLongExtra(PARAM_VIBRATIONDURATION, 0),
intent.getLongExtra(PARAM_SLEEPDURATION, 0)};
//Set vibrator
Vibrator v = (Vibrator) this.getSystemService(Context.VIBRATOR_SERVICE);
//Continue vibrating
stopServiceRequest = false;
while(true){
Log.i("VibrationService", "Vibration is up and running");
v.vibrate(pattern, 1);
try {
Thread.sleep(pattern[0] + pattern[1] + pattern[2]);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(stopServiceRequest){
v.cancel();
Log.i("VibrationService", "Stop service request. End service.");
break;
}
}
}
#Override
public void onDestroy() {
Log.i("VibrationService", "OnDestroy");
stopServiceRequest = true;
super.onDestroy();
}
}

sound not playing in android > icecream sandwich

I was using following code to play sound. Everything worked fine before ICS. But on ICS and higher versions no sound is heard. There is no error, but no sound can be heard.
EDIT: Note, the following code is triggered by a broadcase receiver. BroadCast receiver invokes a async task. In the post process method of asycn task the following method is called.
What could the error possibly be?
public static void playSound(final Context context, final int volume,
Uri uri, final int stream, int maxTime, int tickTime) {
//stopPlaying();
/*
if (stream < 0 || stream > 100) {
throw new IllegalArgumentException(
"volume must be between 0 and 100 .Current volume "
+ volume);
}*/
final AudioManager mAudioManager = (AudioManager) context
.getSystemService(Context.AUDIO_SERVICE);
int deviceLocalVolume = getDeviceVolume(volume,
mAudioManager.getStreamMaxVolume(stream));
Log.d(TAG,
"device max volume = "
+ mAudioManager.getStreamMaxVolume(stream)
+ " for streamType " + stream);
Log.d(TAG, "playing sound " + uri.toString()
+ " with device local volume " + deviceLocalVolume);
final int oldVolume = mAudioManager.getStreamVolume(stream);
// set the volume to what we want it to be. In this case it's max volume
// for the alarm stream.
Log.d(Constants.APP_TAG, "setting device local volume to " + deviceLocalVolume);
mAudioManager.setStreamVolume(stream, deviceLocalVolume,
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
final MediaPlayer mediaPlayer = new MediaPlayer();
golbalMMediaPlayer = mediaPlayer;
try {
final OnPreparedListener OnPreparedListener = new OnPreparedListener() {
#Override
public void onPrepared(final MediaPlayer mp) {
Log.d(TAG, "onMediaPlayercompletion listener");
mp.start();
countDownTimer.start();
}
};
mediaPlayer.setDataSource(context.getApplicationContext(), uri);
mediaPlayer.setAudioStreamType(stream);
mediaPlayer.setLooping(false);
mediaPlayer.setOnPreparedListener(OnPreparedListener);
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
Log.d(Constants.APP_TAG, "Entered onCompletion listener of mediaplayer");
mAudioManager.setStreamVolume(stream, oldVolume,
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
try{
if(mediaPlayer != null && mediaPlayer.isPlaying()){
mediaPlayer.release();
}
}catch(Exception ex){
Log.e(Constants.APP_TAG, "error on oncompletion listener" ,ex);
}
}
});
CountDownTimer timer = new CountDownTimer(maxTime*1000, tickTime*1000) {
#Override
public void onTick(long millisUntilFinished) {
Log.d(TAG, "tick while playing sound ");
}
#Override
public void onFinish() {
Log.d(TAG, "timer finished");
stopPlaying();
}
};
countDownTimer = timer;
mediaPlayer.prepareAsync();
} catch (Exception e) {
Log.e(TAG, "problem while playing sound", e);
} finally {
}
}
LOGS:
:07-01 00:00:00.030: D/beephourly(9500): device max volume = 7 for streamType 5
07-01 00:00:00.030: D/beephourly(9500): playing sound content://media/internal/audio/media/166 with device local volume 7
07-01 00:00:00.030: D/beephourly(9500): setting device local volume to 7
07-01 00:00:00.080: D/beephourly(9500): vibrating with pattern = [J#428bae20
07-01 00:00:00.090: D/beephourly(9500): will show normal notification
07-01 00:00:00.100: D/beephourly(9500): notification is enabled
07-01 00:00:00.100: D/usersettings(9500): hr = 0
07-01 00:00:00.110: D/beephourly(9500): onMediaPlayercompletion listener
07-01 00:00:00.451: D/beephourly(9500): tick while playing sound
07-01 00:00:20.460: D/beephourly(9500): timer finished
07-01 00:00:20.460: D/beephourly(9500): got request to stop playing
07-01 00:00:20.460: D/beephourly(9500): cancelling countdowntimer
07-01 00:00:20.460: D/beephourly(9500): releasing mediaplayer now
Try this :
Playing sound
public class PlaySound extends Activity implements OnTouchListener {
private SoundPool soundPool;
private int soundID;
boolean loaded = false;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
View view = findViewById(R.id.textView1);
view.setOnTouchListener(this);
// Set the hardware buttons to control the music
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
// Load the sound
soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
soundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
#Override
public void onLoadComplete(SoundPool soundPool, int sampleId,
int status) {
loaded = true;
}
});
soundID = soundPool.load(this, R.raw.sound1, 1);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// Getting the user sound settings
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
float actualVolume = (float) audioManager
.getStreamVolume(AudioManager.STREAM_MUSIC);
float maxVolume = (float) audioManager
.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
float volume = actualVolume / maxVolume;
// Is the sound loaded already?
if (loaded) {
soundPool.play(soundID, volume, volume, 1, 0, 1f);
Log.e("Test", "Played sound");
}
}
return false;
}
}
Layout file :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Click on the screen to start playing" >
</TextView>
</LinearLayout>
Source link : http://www.vogella.com/tutorials/AndroidMedia/article.html#sound
Sometimes MediaPlayer objects have to be declared as a public variable or they will be deleted by the Dalvik Heap.
public final MediaPlayer mediaPlayer = new MediaPlayer();
private MediaPlayer mPlayer;
....
SoundPool sp = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
int iTmp = sp.load(getBaseContext(), R.raw.windows_8_notify, 1);
sp.play(iTmp, 1, 1, 0, 0, 1);
mPlayer = MediaPlayer.create(getBaseContext(), R.raw.windows_8_notify);
mPlayer.start();
mPlayer.setLooping(true); }
First , where all your privates are , before the onCreate, put the first line, then, Inside the onCreate start the music, just make sure to change the "windows_8_notify" to the name of the song you want.
I would Wrap the call in an IllegalStateException, run it through the debugger and see what you get.
Things to try
Set the boolean isPlaying=mp.isPlaying(); and check its value.
Try a mp.reset() before starting and see if it works.
Implement MediaPlayer.OnErrorListener and register the method with the media player.
See what error you get. This might be helpful.
LOGS
...streamType 5
StreamType 5 means STREAM_NOTIFICATION.
(Called from notification?)
It should be STREAM_MUSIC (3)
To check it's not ICS/device specific problem,
- place a sound file (sound_01.ogg or sound_01.mp3) under res/raw/ folder
- place buttons named start_button and stop_button in main_layout
and try this.
(I've checked this code with API10 and API19 emulator and sounds are played.)
import android.app.Activity;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
public class MainActivity extends Activity
// implements MediaPlayer.OnPreparedListener
{
private MediaPlayer mediaPlayer;
private boolean isPrepared;
private boolean isPlaying;
private View start_button;
private View stop_button;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
Init();
}
#Override
protected void onResume()
{
super.onResume();
Load();
}
#Override
protected void onPause()
{
super.onPause();
Unload();
}
private void Init()
{
setVolumeControlStream(AudioManager.STREAM_MUSIC);
start_button = findViewById(R.id.start_button);
stop_button = findViewById(R.id.stop_button);
start_button.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
Play();
}
});
stop_button.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
Stop();
}
});
}
private void Load()
{
Unload();
// load from resource (res/raw/xx.ogg or .mp3)
// It's better to use thread
mediaPlayer = MediaPlayer.create(MainActivity.this, R.raw.sound_01); // On success, prepare() will already have been called
// mediaPlayer.setOnPreparedListener(this); // cannot set this listener (MediaPlayer.create does not return before prepared)
isPrepared = true;
}
private void Unload()
{
isPrepared = false;
if (null != mediaPlayer)
{
mediaPlayer.release();
mediaPlayer = null;
}
}
// #Override
// public void onPrepared(MediaPlayer mp)
// {
// isPrepared = true;
// }
private void Play()
{
// If you got "start called in state xx" error, no sound will be heard.
// To reset this error, call reset(), setDataSource() and prepare()
// (for resources: call release() and create())
if (!isPrepared)
{
return;
}
mediaPlayer.start();
isPlaying = true;
}
private void Stop()
{
// Do not omit this check
// or you will get "called in wrong state" errors
// like "pause called in state 8"
// and error (-38, 0)
if (!isPlaying)
{
return;
}
isPlaying = false;
mediaPlayer.pause();
mediaPlayer.seekTo(0);
}
}
If it's ICS/device specific, these links may help. (A little old...)
after small sound is played, no sound will be heard
Issue 35861: Low Volume sound cut out - ICS Galaxy Note
audio focus bug
Issue 1908: No Audio with Android 4.0.4 ICS Galaxy Tab 10.1
device specific problem
No sound during calls (samsung galaxy s3 problem)
You might have a problem if you are using other AsyncTasks or the SerialExecutor in another task elsewhere in your program (and you may not even know it if you are using third party SDK's).
See the post here:
https://code.google.com/p/android/issues/detail?id=20941
I'm suggesting this because your sound "tick" isn't working either. So it isn't a matter of AudioPlayer executing with an incorrect setting necessarily, but rather some other task appears to be blocking it until that task stops, and it probably is a task that runs concurrently with when you expect to hear sound.

Categories