Radio with station recording in Java

Hi everyone! As I said in my first post , I am not a programmer, but rather an amateur. I tried to write my crafts in different languages, but I started with Java. Most of all from the Java family, I liked the JavaFX platform. More precisely, a bunch of JavaFX + FXML, where we write the logic in the controller, and describe the graphical interface in a separate fxml file. The radio is just written using this bundle.





The JLayer library is used for playback. The built-in class MediaPlayer for some reason refused to work for me. Recording and playback are done in separate streams. For the sake of experiment, I tried to start playback in the main thread of the application. I got a tightly frozen interface. I got the same thing when I tried to write in the main thread.





GitHub. NetBeans 8.2 Scene Builder Gluon. , , , , .





:





«Station» , . «Record» , . «Reference» « », .





. . - , , .





<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="535.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="radioplayer.PlayerController">
   <ListView fx:id="stationsListView" focusTraversable="false" layoutX="14.0" layoutY="36.0" prefHeight="246.0" prefWidth="200.0" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" AnchorPane.topAnchor="36.0" />
   <Button fx:id="playButton" focusTraversable="false" layoutX="240.0" layoutY="177.0" mnemonicParsing="false" onAction="#playAction" prefHeight="103.0" prefWidth="130.0" text="PLAY" AnchorPane.bottomAnchor="14.0" AnchorPane.rightAnchor="165.0">
      <font>
         <Font name="System Bold" size="22.0" />
      </font></Button>
   <Button fx:id="stopButton" focusTraversable="false" layoutX="391.0" layoutY="177.0" mnemonicParsing="false" onAction="#stopAction" prefHeight="103.0" prefWidth="130.0" text="STOP" AnchorPane.bottomAnchor="14.0" AnchorPane.rightAnchor="14.0">
      <font>
         <Font name="System Bold" size="22.0" />
      </font></Button>
   <Label fx:id="nameStation" layoutX="240.0" layoutY="46.0" prefHeight="113.0" prefWidth="279.0" wrapText="true" AnchorPane.bottomAnchor="141.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="36.0">
      <font>
         <Font name="System Bold Italic" size="24.0" />
      </font></Label>
   <MenuBar prefHeight="29.0" prefWidth="535.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
     <menus>
       <Menu mnemonicParsing="false" text="Station">
         <items>
           <MenuItem mnemonicParsing="false" onAction="#addAction" text="Add" />
           <MenuItem mnemonicParsing="false" onAction="#editAction" text="Edit" />
           <MenuItem mnemonicParsing="false" onAction="#deleteAction" text="Delete" />
         </items>
       </Menu>
       <Menu mnemonicParsing="false" text="Record">
         <items>
           <MenuItem fx:id="recordItem" mnemonicParsing="false" onAction="#recordAction" text="To begin" />
           <MenuItem fx:id="stopRecordItem" mnemonicParsing="false" onAction="#stopRecordAction" text="Stop" />
           <MenuItem mnemonicParsing="false" onAction="#directoryRecordAction" text="Records Directory" />
         </items>
       </Menu>
       <Menu mnemonicParsing="false" text="Reference">
         <items>
           <MenuItem mnemonicParsing="false" onAction="#appInfoAction" text="About the program" />
           <MenuItem mnemonicParsing="false" onAction="#exitAction" text="Exit" />
         </items>
       </Menu>
     </menus>
   </MenuBar>
</AnchorPane>
      
      



(toast . ):





.root{
    -fx-background-color: grey;
}
.button{
    -fx-background-radius: 40;
    -fx-border-radius: 40;
    -fx-text-fill: white;
}
.button:hover{
    -fx-background-color: derive(-fx-base, 18%);
    -fx-border-style: solid;
    -fx-border-width: 1;
    -fx-border-color: derive(-fx-base, -15%);
    -fx-cursor: hand;
}
.button:pressed{
    -fx-text-fill: black;
}
.list-view, .list-view .viewport, .list-view .content{
    -fx-background-color: gainsboro;
}
.list-view:hover{
    -fx-cursor: hand;
}
.toast{
    -fx-background-radius: 30;
    -fx-border-radius: 30;
    -fx-background-color: black;
    -fx-padding: 20;
}
#nameStation{
    -fx-text-fill: white;
}
#playButton{
    -fx-background-color: blue;
}
#stopButton{
    -fx-background-color: red;
}
      
      



:





taskPlayer = new Task() {
            @Override
            public Void call() {
                try {
                    radioUrl = new URL(urlString);
                    InputStream in = radioUrl.openStream();
                    InputStream is = new BufferedInputStream(in);
                    player = new Player(is);
                    player.play();
                } catch (FileNotFoundException e) {
                    e.getMessage();
                } catch (IOException | JavaLayerException e) {
                    e.getMessage();
                }
                return null;
            }
        };
        new Thread(taskPlayer).start();
      
      



, . , JLayer. :





taskRecord=new Task() {
            @Override
            public Void call() throws FileNotFoundException, IOException{
                    output = new FileOutputStream(reader(file.getAbsolutePath())+
                            separator+nameStation.getText()+"-"+new Date().toString().replace(":","-")+".mp3");
                    InputStream in = radioUrl.openStream();
                    InputStream is = new BufferedInputStream(in);
                    byte data[] = new byte[1024];
                    int count;
                    while ((count = is.read(data)) != -1) {
                        output.write(data, 0, count);
                    }
                output.flush();
                return null;
            }
        };
        new Thread(taskRecord).start();
      
      



, , URL. , :





private void createDefaultStations(){
         String[] stationNames = {"NonStopPlay","Classical Music","Fip Radio","Jazz Legends","Joy Radio","Live-icy","Music Radio","Radio Electron","Dubstep","Trancemission"};
         String[] stationUrls = {"http://stream.nonstopplay.co.uk/nsp-128k-mp3","http://stream.srg-ssr.ch/m/rsc_de/mp3_128","http://direct.fipradio.fr/live/fip-midfi.mp3","http://jazz128legends.streamr.ru/","http://airtime.joyradio.cc:8000/airtime_192.mp3","http://live-icy.gss.dr.dk:8000/A/A05H.mp3","http://ice-the.musicradio.com/CapitalXTRANationalMP3","http://radio-electron.ru:8000/128","http://air.radiorecord.ru:8102/dub_320","http://air.radiorecord.ru:8102/tm_320"};
         for(int i=0;i<10;i++){
             writer(path+separator+stationNames[i], stationUrls[i]);
         }
    }
      
      



dirCreator, RadioStations, . :





private void dirCreator(final String fPath) {
        final File file = new File(fPath);
        if (!file.exists()) {
            file.mkdir();
            if(file.exists()){
                alertWindow("The <RadioStations> directory has been created.\nYour radio stations will be here:\n"+fPath);
                createDefaultStations();
            }else{
                alertWindow("Error!\nThe <RadioStations> directory will not be created.\n" +
                        "Try creating the specified directory manually in the following path:\n"+fPath+"\nThe program will be closed.");
                System.exit(0);
            }
        }
    }
      
      



. , :





private boolean permissionRead(File file){
        if(!file.canRead()){
            file.setReadable(true);
            return !file.canRead();
        }
        return false;
    }
    private boolean permissionWrite(File file){
        if(!file.canWrite()){
            file.setWritable(true);
            return !file.canWrite();
        }
        return false;
    }
      
      



RadioStations:





@Override
    public void initialize(URL url, ResourceBundle rb) {
        parentPath = System.getProperty("user.home");
        path=parentPath+separator+"RadioStations";
        this.dirCreator(this.path);
        File f=new File(path);
        if(permissionRead(f)||permissionWrite(f)){
            if(permissionRead(f)&&permissionWrite(f)){
                alertWindow("Failed to get permission to read and write files to the <RadioStations> directory.\nTry to give permission manually.");
            }else if(permissionRead(f)){
                alertWindow("Failed to get permission to read files in directory <RadioStations>.\nTry to give permission manually.");
            }else{
                alertWindow("Failed to get permission to write files to <RadioStations> directory.\nTry to give permission manually.");
            }
            System.exit(0);
        }
        showStationsList();
        stopButton.setDisable(true);
        recordItem.setDisable(true);
        stopRecordItem.setDisable(true);
    }   
      
      



, . , , . :





final Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        alert.setResizable(true);
        alert.getDialogPane().setPrefSize(500,200);
        alert.setTitle("Saving Recordings");
        alert.setHeaderText("");
        alert.setContentText("The default path for your recordings is:\n"+f.getAbsolutePath()+"\nChange?");
        
        ButtonType buttonTypeEdit = new ButtonType("Edit", ButtonBar.ButtonData.OK_DONE);
        ButtonType buttonTypeDefault = new ButtonType("Default", ButtonBar.ButtonData.FINISH);
        ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
        
        alert.getButtonTypes().setAll(buttonTypeEdit, buttonTypeDefault, buttonTypeCancel);
        
        final Optional<ButtonType> resultAlert = alert.showAndWait();
      
      



:





, . «Edit», , «Default», . «Cancel» .





. :





Dialog dialog = new Dialog<>();
        dialog.setTitle("Station Creation");
        dialog.setHeaderText("Enter the name and url of the radio station");

        ButtonType createButtonType = new ButtonType("Create", ButtonBar.ButtonData.OK_DONE);
        ButtonType cancelButtonType  = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
        dialog.getDialogPane().getButtonTypes().addAll(createButtonType,cancelButtonType);

        GridPane grid = new GridPane();
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(20, 150, 10, 10));

        TextField stationName = new TextField();
        TextField url = new TextField();

        grid.add(new Label("Title:"), 0, 0);
        grid.add(stationName, 1, 0);
        grid.add(new Label("Url:"), 0, 1);
        grid.add(url, 1, 1);

        dialog.getDialogPane().setContent(grid);

        Optional<ButtonType> result = dialog.showAndWait();
      
      



. . :





, .





. . .





package radioplayer;

import javafx.application.Application;
import java.awt.*;
import javafx.stage.Stage;
/**
 *
 * @author alex
 */
public class Splash extends Application{
    
    public static void main(final String[] args) {
        SplashScreen splash = SplashScreen.getSplashScreen();
        try {
            Thread.sleep(3000L);
        }
        catch (InterruptedException ex) {
            ex.getMessage();
        }
        if (splash != null) {
            splash.close();
            Application.launch(RadioPlayer.class, args);
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
}
      
      



:





:





-splash:src/images/splash.png





:





SplashScreen-Image: images/splash.png
      
      



, Android

, Android OS. :





:





package radioplayer;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
/**
 *
 * @author alex
 */
public class Toast {
    void setMessage(final String toastMsg){
        Stage toastStage=new Stage();
        toastStage.setResizable(false);
        toastStage.initStyle(StageStyle.TRANSPARENT);
        Text t = new Text(toastMsg);
        t.setFont(Font.font("Verdana",20));
        t.setFill(Color.WHITE);
        StackPane root = new StackPane(t);
        root.getStyleClass().add("toast");
        root.setOpacity(0);
        Scene scene = new Scene(root);
        scene.getStylesheets().add((getClass().getResource("style.css")).toExternalForm());
        scene.setFill(null);
        toastStage.setScene(scene);
        toastStage.show();
        Timeline tl1 = new Timeline();
        KeyFrame fadeInKey1 = new KeyFrame(Duration.millis(500),new KeyValue (toastStage.getScene().getRoot().opacityProperty(), 1));
        tl1.getKeyFrames().add(fadeInKey1);
        tl1.setOnFinished((ae) ->
                new Thread(() -> {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.getMessage();
                    }
                    Timeline tl2 = new Timeline();
                    KeyFrame fadeOutKey1 = new KeyFrame(Duration.millis(500), new KeyValue(toastStage.getScene().getRoot().opacityProperty(), 0));
                    tl2.getKeyFrames().add(fadeOutKey1);
                    tl2.setOnFinished((aeb) -> toastStage.close());
                    tl2.play();
                }).start());
        tl1.play();
    }
}
      
      



, NetBeans , , JLayer. . , . 





JLayer , build.xml :





<target name="package-for-store" depends="jar">
    <property name="store.jar.name" value="Radio"/>
    <property name="store.dir" value="store"/>
    <property name="store.jar" value="${store.dir}/${store.jar.name}.jar"/>
    <echo message="Packaging ${application.title} into a single JAR at ${store.jar}"/>
    <delete dir="${store.dir}"/>
    <mkdir dir="${store.dir}"/>
    <jar destfile="${store.dir}/temp_final.jar" filesetmanifest="skip">
        <zipgroupfileset dir="dist" includes="*.jar"/>
        <zipgroupfileset dir="dist/lib" includes="*.jar"/>
        <manifest>
            <attribute name="Main-Class" value="radioplayer.Splash"/>
            <attribute name="SplashScreen-Image" value="images/splash.png"/>
        </manifest>
    </jar>
    <zip destfile="${store.jar}">
        <zipfileset src="${store.dir}/temp_final.jar"
        excludes="META-INF/*.SF, META-INF/*.DSA, META-INF/*.RSA"/>
    </zip>
    <delete file="${store.dir}/temp_final.jar"/>
</target>
      
      



« », «package-for-store». «store» .





SourceForge. !








All Articles