com.shinymayhem.radiopresets.ServiceAudioFormat.java Source code

Java tutorial

Introduction

Here is the source code for com.shinymayhem.radiopresets.ServiceAudioFormat.java

Source

/*
 * Copyright (C) 2013 Reese Wilson | Shiny Mayhem
    
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
    
   http://www.apache.org/licenses/LICENSE-2.0
    
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
package com.shinymayhem.radiopresets;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpException;

import android.app.IntentService;
import android.content.Intent;
import android.os.IBinder;
import android.webkit.URLUtil;

public class ServiceAudioFormat extends IntentService {
    private static final boolean LOCAL_LOGV = ActivityMain.LOCAL_LOGV;
    private static final boolean LOCAL_LOGD = ActivityMain.LOCAL_LOGD;
    private static final String TAG = "ServiceAudioFormat";

    private enum AudioType {
        MP3, M3U, PLS, XSPF, AAC, AACP, UNKNOWN
    };

    protected ActivityLogger mLogger = new ActivityLogger(this);
    protected InputStream mStream;

    public ServiceAudioFormat() {
        super("ServiceAudioFormatName");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (LOCAL_LOGV)
            log("onHandleIntent()", "v");
        String url = intent.getStringExtra(ServiceRadioPlayer.EXTRA_URL);
        Intent updateIntent = new Intent(this, ServiceRadioPlayer.class);
        try {
            AudioType type = this.processUrl(url);
            String newUrl = this.getUrlByType(type, url);
            if (!url.equals(newUrl)) {
                if (LOCAL_LOGD)
                    log("New URL: " + newUrl, "d");
            } else {
                if (LOCAL_LOGV)
                    log("New URL same as old URL: " + newUrl, "v");
            }

            updateIntent.setAction(ServiceRadioPlayer.ACTION_PLAY_STREAM);
            updateIntent.putExtra(ServiceRadioPlayer.EXTRA_URL, newUrl);
            boolean updateUrl;
            //update player url if it was a playlist and the audio stream url was resolved
            //don't update if url was already a streaming media url 
            //TODO handle the case where metadata is in the playlist 
            switch (type) {
            case M3U:
            case PLS:
            case XSPF:
                updateUrl = true;
                break;
            case AAC:
            case AACP:
                updateIntent.setAction(ServiceRadioPlayer.ACTION_UNSUPPORTED_FORMAT_ERROR);
            default:
                updateUrl = false;
            }
            updateIntent.putExtra(ServiceRadioPlayer.EXTRA_FORMAT, type.toString());
            updateIntent.putExtra(ServiceRadioPlayer.EXTRA_UPDATE_URL, updateUrl);
        } catch (StreamHttpException e) {
            if (LOCAL_LOGD)
                log("StreamHttpException for " + url, "d");
            updateIntent.setAction(ServiceRadioPlayer.ACTION_STREAM_ERROR);
            updateIntent.putExtra(ServiceRadioPlayer.EXTRA_RESPONSE_CODE, e.getResponseCode());
            updateIntent.putExtra(ServiceRadioPlayer.EXTRA_RESPONSE_MESSAGE, e.getResponseMessage());
        } catch (MalformedURLException e) {
            if (LOCAL_LOGD)
                log("MalformedURLException for " + url, "d");
            updateIntent.setAction(ServiceRadioPlayer.ACTION_FORMAT_ERROR);
            updateIntent.putExtra(ServiceRadioPlayer.EXTRA_ERROR_MESSAGE,
                    getResources().getString(R.string.error_url));
        } catch (IOException e) {
            if (LOCAL_LOGD)
                log("IOException for " + url, "d");
            updateIntent.setAction(ServiceRadioPlayer.ACTION_FORMAT_ERROR);
            updateIntent.putExtra(ServiceRadioPlayer.EXTRA_ERROR_MESSAGE,
                    getResources().getString(R.string.error_unknown));
        }

        startService(updateIntent);
        //stopSelf();

    }

    protected void handleHttpResponse(int responseCode, String message) throws IOException, StreamHttpException {
        //String message = con.getResponseMessage();
        if (LOCAL_LOGD)
            log("Server response code:" + String.valueOf(responseCode) + ", message:" + message, "d");
        if (responseCode < 200 || responseCode >= 300) {
            throw new StreamHttpException(responseCode, message);
        }

    }

    protected AudioType processUrl(String url) throws StreamHttpException, IOException {
        //TODO make recursive, if playlist within playlist
        //TODO look into checking response headers anyway
        //check url first, it is fastest, no network connections needed
        AudioType type = getTypeFromString(url);
        //now try to handle cases where type could not be determined by url
        if (type.equals(AudioType.UNKNOWN)) {
            //URL streamUrl;
            /*streamUrl = new URL(url);
                
            HttpURLConnection con;
            con = (HttpURLConnection)streamUrl.openConnection();
            con.setRequestProperty("Connection", "close");
            con.connect();
            */

            Map<String, List<String>> headers = this.getInputStream(url); //con.getInputStream();

            //InputStream stream = con.getInputStream();
            if (headers.containsKey("Content-Type")) {
                // Headers are sent via HTTP
                List<String> contentTypes = headers.get("Content-Type");
                for (String contentType : contentTypes) {
                    if (LOCAL_LOGD)
                        log("Content-Type:" + contentType, "d");
                    type = this.getTypeFromContentType(contentType);
                    if (!type.equals(AudioType.UNKNOWN)) //found type
                    {
                        break;
                    }
                }

            } else {
                if (LOCAL_LOGD)
                    log("No content type", "d");
            }
            //if it still can't find it, try one last time in the disposition
            if (type.equals(AudioType.UNKNOWN) && headers.containsKey("Content-Disposition")) {
                List<String> dispositions = headers.get("Content-Disposition");
                for (String disposition : dispositions) {
                    if (LOCAL_LOGD)
                        log("Disposition:" + disposition, "d");
                    type = this.getTypeFromString(disposition);
                    if (!type.equals(AudioType.UNKNOWN)) //found type
                    {
                        break;
                    }
                }
            }
            mStream.close();

        }
        return type;
    }

    private AudioType getTypeFromString(String string) {
        AudioType type = AudioType.UNKNOWN;
        if (string.contains(".mp3")) {
            type = AudioType.MP3;
        } else if (string.contains(".m3u")) {
            type = AudioType.M3U;
        } else if (string.contains(".pls")) {
            type = AudioType.PLS;
        } else if (string.contains(".xspf")) {
            type = AudioType.XSPF;
        } else if (string.contains(".aac")) {
            type = AudioType.AAC;
        } else if (string.contains(".aacp")) {
            type = AudioType.AACP;
        }
        return type;
    }

    private AudioType getTypeFromContentType(String contentType) {
        AudioType type = AudioType.UNKNOWN;
        if (contentType.contains("audio/x-scpls") || contentType.contains("audio/x-scpls")) {
            type = AudioType.PLS;
        } else if (contentType.contains("audio/mpegurl") || contentType.contains("audio/x-mpegurl")) {
            type = AudioType.M3U;
        } else if (contentType.contains("application/xspf+xml") || contentType.contains("audio/x-mpegurl")) {
            type = AudioType.XSPF;
        } else if (contentType.equals("audio/mpeg")) {
            type = AudioType.MP3;
        } else if (contentType.equals("audio/aac")) {
            type = AudioType.AAC;
        } else if (contentType.equals("audio/aacp")) {
            type = AudioType.AACP;
        }
        return type;
    }

    private String getUrlByType(AudioType type, String url) throws IOException, StreamHttpException {
        if (LOCAL_LOGD)
            log("Getting url by type:" + type.toString(), "d");
        String newUrl = url;
        switch (type) {
        case UNKNOWN:
            break;
        case AAC:
            break;
        case AACP:
            break;
        case M3U:
            newUrl = this.getUrlFromM3u(url);
            break;
        case MP3:
            //already a stream
            break;
        case PLS:
            newUrl = this.getUrlFromPls(url);
            break;
        case XSPF:
            newUrl = this.getUrlFromXspf(url);
            break;
        default:
            break;

        }
        return newUrl;
    }

    private String getUrlFromM3u(String url) throws IOException, StreamHttpException {
        if (LOCAL_LOGD)
            log("Get URL from M3U", "d");
        if (mStream == null) {
            getInputStream(url);
        }
        String newUrl = url;
        BufferedReader reader = new BufferedReader(new InputStreamReader(mStream));
        String line;
        while ((line = reader.readLine()) != null) {
            if (LOCAL_LOGV)
                log("read line:" + line, "v");
            if (!line.startsWith("#") && URLUtil.isHttpUrl(line) || URLUtil.isHttpsUrl(line)) {
                newUrl = line;
                break;
            }
        }
        return newUrl;
    }

    private String getUrlFromPls(String url) throws IOException, StreamHttpException {
        if (LOCAL_LOGD)
            log("Get URL from PLS", "d");
        if (mStream == null) {
            getInputStream(url);
        }
        String newUrl = url;
        BufferedReader reader = new BufferedReader(new InputStreamReader(mStream));
        String line;
        while ((line = reader.readLine()) != null) {
            if (LOCAL_LOGV)
                log("read line:" + line, "v");
            if (line.startsWith("File")) {
                newUrl = line.substring(line.indexOf("=") + 1);
                break;
            }
        }
        return newUrl;
    }

    //TODO
    private String getUrlFromXspf(String url) throws IOException, StreamHttpException {
        if (LOCAL_LOGD)
            log("Get URL from XSPF", "d");
        if (mStream == null) {
            getInputStream(url);
        }
        String newUrl = url;
        BufferedReader reader = new BufferedReader(new InputStreamReader(mStream));
        String line;
        while ((line = reader.readLine()) != null) {
            if (LOCAL_LOGV)
                log("read line:" + line, "v");
        }
        return newUrl;
    }

    private Map<String, List<String>> getInputStream(String url) throws IOException, StreamHttpException {
        /*
        URL streamUrl = new URL(url);
        StreamURLConnection con;
        con = (StreamURLConnection)streamUrl.openConnection();
        con.setRequestProperty("Connection", "close");
        con.connect();
        int responseCode = con.getResponseCode(); 
        if (responseCode < 200 || responseCode >= 300)
        {
        this.handleHttpError(con);
        }*/
        URL streamUrl = new URL(url);
        HttpURLConnection con;
        con = (HttpURLConnection) streamUrl.openConnection();
        con.setRequestProperty("Connection", "close");
        con.connect();
        Map<String, List<String>> headers = con.getHeaderFields();
        String statusLine = headers.get(null).get(0); //con.getHeaderField(0);
        //String statusLine2 = con.getHeaderField(0);
        String message = statusLine;
        int responseCode = -1;
        if (statusLine.startsWith("HTTP/1.") || statusLine.startsWith("ICY")) {
            int codePos = statusLine.indexOf(' ');
            if (codePos > 0) {

                int phrasePos = statusLine.indexOf(' ', codePos + 1);
                if (phrasePos > 0 && phrasePos < statusLine.length()) {
                    message = statusLine.substring(phrasePos + 1);
                }

                if (phrasePos < 0)
                    phrasePos = statusLine.length();

                try {
                    responseCode = Integer.parseInt(statusLine.substring(codePos + 1, phrasePos));
                } catch (NumberFormatException e) {
                }
            }
        }

        this.handleHttpResponse(responseCode, message);
        mStream = con.getInputStream();
        return headers;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

    private class StreamHttpException extends HttpException {
        private static final long serialVersionUID = 2499906481918509156L;
        private int mResponseCode;
        private String mResponseMessage;

        public StreamHttpException(int responseCode, String responseMessage) {
            this.mResponseCode = responseCode;
            this.mResponseMessage = responseMessage;
        }

        public int getResponseCode() {
            return mResponseCode;
        }

        public String getResponseMessage() {
            return mResponseMessage;
        }
    }

    private void log(String text, String level) {
        mLogger.log(TAG, text, level);
    }

}