Package com.pugh.sockso.web.action

Source Code of com.pugh.sockso.web.action.Streamer

package com.pugh.sockso.web.action;

import com.pugh.sockso.music.stream.MusicStream;
import com.pugh.sockso.Constants;
import com.pugh.sockso.Properties;
import com.pugh.sockso.Utils;
import com.pugh.sockso.db.Database;
import com.pugh.sockso.music.Track;
import com.pugh.sockso.music.encoders.BuiltinEncoder;
import com.pugh.sockso.music.encoders.CustomEncoder;
import com.pugh.sockso.music.encoders.Encoder;
import com.pugh.sockso.music.encoders.Encoders;
import static com.pugh.sockso.music.encoders.Encoders.Type.BUILTIN;
import static com.pugh.sockso.music.encoders.Encoders.Type.CUSTOM;
import com.pugh.sockso.music.stream.ChunkedStream;
import com.pugh.sockso.music.stream.RangeStream;
import com.pugh.sockso.music.stream.RangeStream.Range;
import com.pugh.sockso.music.stream.SimpleStream;
import com.pugh.sockso.web.BadRequestException;
import com.pugh.sockso.web.Request;
import com.pugh.sockso.web.Response;
import com.pugh.sockso.web.User;

import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

public class Streamer extends BaseAction {

    private static final Logger log = Logger.getLogger( Streamer.class );


    /**
     *  handles a request
     *
     */
   
    public void handleRequest() throws SQLException, IOException, BadRequestException {
       
        final Request req = getRequest();
        final int trackId = Integer.parseInt( req.getUrlParam( 1 ) );

        final Track track = Track.find( getDatabase(), trackId );

        if ( track == null ) {
            throw new BadRequestException( "Invalid track ID", 404 );
        }

        final MusicStream stream = getStream( track );

        playTrack( track, stream );

    }
   
    /**
     *  if users are required to login then authentication will be done via session
     *  information send in the playlists.
     *
     */
   
    @Override
    public boolean requiresLogin() {

        final Properties p = getProperties();

        return p.get( Constants.STREAM_REQUIRE_LOGIN ).equals( Properties.YES );

    }

    protected MusicStream getStream( final Track track ) throws IOException, BadRequestException {

        MusicStream stream;

        // check if we're using an encoder first
        Encoder encoder = getEncoder( track );

        if ( encoder != null ) {
            // use Chunked stream strategy
            stream = new ChunkedStream( track, encoder, getProperties() );

        }
        else if ( hasRangeHeader() ) {
            // stream from range
            Range range = processRangeRequest(track);
            stream = new RangeStream( track, range );
        }
        else {
            // stream normally (unaltered)
            stream = new SimpleStream( track );
        }

        return stream;

    }

    /**
     *  streams a particular track to the response object
     *
     *  @param trackId the track id to playTrack
     *
     *  @throws SQLException
     *  @throws IOException
     *  @throws BadRequestException
     *
     */

    protected void playTrack( final Track track, final MusicStream stream ) throws SQLException, IOException, BadRequestException {

        logTrackPlayed( track );

        final Response response = getResponse();
        stream.setHeaders( response );
        response.sendHeaders();
        stream.sendAudioStream( new DataOutputStream(response.getOutputStream()) );
           
    }


    protected Encoder getEncoder( final Track track ) throws IOException {

        final Properties p = getProperties();
        final String ext = Utils.getExt(track.getPath());

        final String type = p.get(Constants.PROP_ENCODERS_PREFIX + ext);

        if ( type.equals("") ) {
            return null;
        }

        Encoders.Type encoderType;

        try {
            encoderType = Encoders.Type.valueOf(type);
        } catch (IllegalArgumentException e) {
            log.error("Encoder type not found", e);
            return null;
        }

        switch (encoderType) {

            // 1. use a builtin encoder?
            case BUILTIN:

                final String name = p.get(Constants.PROP_ENCODERS_PREFIX + ext + ".name");
                final BuiltinEncoder encoder = Encoders.getBuiltinEncoderByName(name).getEncoder();

                if ( encoder != null ) {

                    return encoder;
                }

            // 2. use a custom command to encode?
            case CUSTOM:

                final String command = p.get(Constants.PROP_ENCODERS_PREFIX + ext + ".command");

                if ( !command.equals("") ) {
                   
                    return new CustomEncoder(command);
                }

            // 3. otherwise stream unaltered (no encoder)
            default:
               
                return null;
        }
       
    }
   
    /**
     * Check if the header "Range" exists in the HTTP Request
     *
     * @return
     */
    private boolean hasRangeHeader() {

        final String rangeHeader = getRequest().getHeader("Range");

        return !rangeHeader.isEmpty();
       
    }

    /**
     *
     * Process range request headers to seek to positions in the audio stream
     *
     * GET /2390/2253727548_a413c88ab3_s.jpg HTTP/1.1
     * Host: farm3.static.flickr.com
     * Range: bytes=1000-
     *
     * HTTP/1.0 206 Partial Content
     *
     * Content-Length: 2980
     * Content-Range: bytes 1000-3979/3980
     *
     */
    protected Range processRangeRequest( final Track track ) throws IOException, BadRequestException {

        final String rangeHeader = getRequest().getHeader("Range");

        final Pattern pattern = Pattern.compile("bytes=(\\d+)-(\\d+)?");
        final Matcher matcher = pattern.matcher(rangeHeader);

        if ( !matcher.matches() ) {
            log.error("Bad \"Range\" header: " + rangeHeader);
            throw new BadRequestException("Invalid range", 416);
        }

        try {
           
            long beginPos = Long.parseLong(matcher.group(1));
            long endPos = -1;

            String endMatch = matcher.group(2);

            if ( endMatch != null ) {
                endPos = Long.parseLong(endMatch);
            }

            final long trackLength = new File(track.getPath()).length();

            if ( endPos < 0 ) {
                endPos = trackLength - 1;
            }

            if ( beginPos < 0 || beginPos >= trackLength
                 || endPos >= trackLength || endPos <= beginPos ) {
                log.error("Bad \"Range\" values: " + beginPos + "-" + endPos);
                throw new BadRequestException("Invalid range", 416);
            }

            return new Range(beginPos, endPos);

        } catch (NumberFormatException e) {
            log.error("Bad \"Range\" header", e);
            throw new BadRequestException("Invalid range", 416);
        }

    }

    /**
     *  @TODO Create a dao class for PlayLog
     *
     *  logs the fact that a track has been played
     *
     *  @param trackId the track id
     *  @param path the path to the file
     * 
     */
   
    protected void logTrackPlayed( final Track track ) {
       
        log.debug( "Track: " + track.getPath() );
       
        PreparedStatement st = null;
               
        try {

            final Database db = getDatabase();
            final User user = getUser();
            final String sql = " insert into play_log ( track_id, date_played, user_id ) " +
                               " values ( ?, current_timestamp, ? ) ";
           
            st = db.prepare( sql );
            st.setInt( 1, track.getId() );
           
            if ( user != null )
                st.setInt( 2, user.getId() );
            else
                st.setNull( 2, Types.INTEGER );

            st.execute();

        }

        catch ( SQLException e ) {
            log.error( e );
        }

        finally {
            Utils.close( st );
        }
       
    }

}
TOP

Related Classes of com.pugh.sockso.web.action.Streamer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.