├── .gitignore
├── LICENSE
├── README.md
├── app
├── build.gradle
├── build
│ └── .gitignore
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── ic_launcher-web.png
│ ├── java
│ └── at
│ │ └── andreasrohner
│ │ └── spartantimelapserec
│ │ ├── DeviceStatusReceiver.java
│ │ ├── ForegroundService.java
│ │ ├── GithubStar.java
│ │ ├── MainActivity.java
│ │ ├── PowerSavingReceiver.java
│ │ ├── PreviewActivity.java
│ │ ├── ScheduleReceiver.java
│ │ ├── ServiceHelper.java
│ │ ├── SettingsCommon.java
│ │ ├── SettingsFragment.java
│ │ ├── data
│ │ ├── RecMode.java
│ │ └── RecSettings.java
│ │ ├── preference
│ │ ├── DateTimePreference.java
│ │ ├── EditSummaryPreference.java
│ │ ├── IntervalPickerPreference.java
│ │ ├── IpInformation.java
│ │ ├── NoKBEditTextPreference.java
│ │ └── SeekBarPreference.java
│ │ ├── recorder
│ │ ├── ImageRecorder.java
│ │ ├── PowerSavingImageRecorder.java
│ │ ├── Recorder.java
│ │ ├── VideoRecorder.java
│ │ └── VideoTimeLapseRecorder.java
│ │ ├── rest
│ │ ├── HttpOutput.java
│ │ ├── HttpThread.java
│ │ ├── ListFolderHtml.java
│ │ ├── ListFolderPlain.java
│ │ ├── ReplyCode.java
│ │ ├── RestService.java
│ │ └── TcpListener.java
│ │ └── sensor
│ │ ├── CameraSettings.java
│ │ ├── MuteShutter.java
│ │ └── OrientationSensor.java
│ └── res
│ ├── drawable
│ ├── ic_camera.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_launcher_monochrome.xml
│ ├── ic_radio_button_checked_24px.xml
│ ├── ic_radio_button_checked_disabled_24px.xml
│ ├── ic_stop_circle_24px.xml
│ ├── ic_stop_circle_disabled_24px.xml
│ ├── ic_visibility_24px.xml
│ └── ic_visibility_disabled_24px.xml
│ ├── layout
│ ├── dialog_date_preference.xml
│ ├── dialog_date_widget_preference.xml
│ ├── dialog_intervalpicker_preference.xml
│ └── dialog_seekbar_preference.xml
│ ├── menu
│ └── main.xml
│ ├── mipmap-anydpi-v26
│ └── ic_launcher.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── raw
│ ├── favicon.ico
│ ├── help.txt
│ └── index.html
│ ├── values-de
│ ├── arrays.xml
│ └── strings.xml
│ ├── values-night
│ └── themes.xml
│ ├── values-tr
│ └── strings.xml
│ ├── values
│ ├── arrays.xml
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ └── preferences.xml
├── build.gradle
├── fastlane
└── metadata
│ └── android
│ ├── de-DE
│ ├── changelogs
│ │ ├── 10.txt
│ │ ├── 11.txt
│ │ ├── 12.txt
│ │ ├── 13.txt
│ │ ├── 14.txt
│ │ ├── 15.txt
│ │ ├── 16.txt
│ │ ├── 17.txt
│ │ └── 18.txt
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
│ ├── en-US
│ ├── changelogs
│ │ ├── 10.txt
│ │ ├── 11.txt
│ │ ├── 12.txt
│ │ ├── 13.txt
│ │ ├── 14.txt
│ │ ├── 15.txt
│ │ ├── 16.txt
│ │ ├── 17.txt
│ │ ├── 18.txt
│ │ └── 19.txt
│ ├── full_description.txt
│ ├── images
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ └── 01.png
│ ├── short_description.txt
│ └── title.txt
│ └── tr-TR
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/*
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Send a coffee to
2 | woheller69@t-online.de
3 |
4 |
5 |
6 | Or via this link (with fees)
7 | 
8 |
9 |
10 | | **RadarWeather** | **Gas Prices** | **Smart Eggtimer** |
11 | |:---:|:---:|:---:|
12 | | [
](https://f-droid.org/packages/org.woheller69.weather/) | [
](https://f-droid.org/packages/org.woheller69.spritpreise/) | [
](https://f-droid.org/packages/org.woheller69.eggtimer/) |
13 | | **Bubble** | **hEARtest** | **GPS Cockpit** |
14 | | [
](https://f-droid.org/packages/org.woheller69.level/) | [
](https://f-droid.org/packages/org.woheller69.audiometry/) | [
](https://f-droid.org/packages/org.woheller69.gpscockpit/) |
15 | | **Audio Analyzer** | **LavSeeker** | **TimeLapseCam** |
16 | | [
](https://f-droid.org/packages/org.woheller69.audio_analyzer_for_android/) |[
](https://f-droid.org/packages/org.woheller69.lavatories/) | [
](https://f-droid.org/packages/org.woheller69.TimeLapseCam/) |
17 | | **Arity** | **Cirrus** | **solXpect** |
18 | | [
](https://f-droid.org/packages/org.woheller69.arity/) | [
](https://f-droid.org/packages/org.woheller69.omweather/) | [
](https://f-droid.org/packages/org.woheller69.solxpect/) |
19 | | **gptAssist** | **dumpSeeker** | **huggingAssist** |
20 | | [
](https://f-droid.org/packages/org.woheller69.gptassist/) | [
](https://f-droid.org/packages/org.woheller69.dumpseeker/) | [
](https://f-droid.org/packages/org.woheller69.hugassist/) |
21 | | **FREE Browser** | **whoBIRD** | **PeakOrama** |
22 | | [
](https://f-droid.org/packages/org.woheller69.browser/) | [
](https://f-droid.org/packages/org.woheller69.whobird/) | [
](https://f-droid.org/packages/org.woheller69.PeakOrama/) |
23 | | **Whisper** | **Seamless** | |
24 | | [
](https://f-droid.org/packages/org.woheller69.whisper/) | [
](https://f-droid.org/packages/org.woheller69.seemless/) | |
25 |
26 | TimeLapseCam
27 | ========================
28 |
29 |
30 |
31 |
32 |
33 | Minimalistic android app that records time lapse videos in the background
34 | with the screen turned off. Time lapse videos can be saved in various resolutions
35 | as a MP4 video file, or as individual JPEG files. If possible the shutter sound
36 | of the camera is muted, so it can be used as a spy camera. While recording
37 | there is no preview visible and the screen can be turned off.
38 | There is no recording limit, but the app will stop recording if battery
39 | level or disk space is low.
40 |
41 | ## Features
42 |
43 | * Record normal video (MP4)
44 | * Record time lapse video (MP4/JPEG)
45 | * Select various resolutions
46 | * Select camera to use
47 | * Schedule recording on specific date
48 | * Preview with selected resolution
49 | * Disable shutter sound
50 |
51 | ## License
52 |
53 | Forked from zeitgeist87/SpartanTimeLapseRecorder which is published under GPL 3.0
54 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | compileSdk 34
7 |
8 | defaultConfig {
9 | applicationId "org.woheller69.TimeLapseCam"
10 | minSdk 23
11 | targetSdk 34
12 | versionCode 19
13 | versionName "1.9"
14 |
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_8
25 | targetCompatibility JavaVersion.VERSION_1_8
26 | }
27 | }
28 |
29 | dependencies {
30 |
31 | implementation "androidx.core:core:1.9.0" //needed for broadcast receiver
32 | implementation 'androidx.appcompat:appcompat:1.5.1'
33 | implementation 'com.google.android.material:material:1.7.0'
34 |
35 | }
--------------------------------------------------------------------------------
/app/build/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
45 |
46 |
47 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/DeviceStatusReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec;
20 |
21 | import android.content.BroadcastReceiver;
22 | import android.content.Context;
23 | import android.content.Intent;
24 | import android.content.SharedPreferences;
25 | import android.preference.PreferenceManager;
26 | import android.util.Log;
27 |
28 | public class DeviceStatusReceiver extends BroadcastReceiver {
29 |
30 | @Override
31 | public void onReceive(Context context, Intent intent) {
32 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
33 |
34 | boolean stopOnLowBattery = prefs.getBoolean("pref_stop_low_battery", true);
35 | boolean stopOnLowStorage = prefs.getBoolean("pref_stop_low_storage", true);
36 | /*
37 | * battery or storage is low if we don't stop the recording the mp4
38 | * files get corrupted and are not playable any more
39 | */
40 | if ((stopOnLowBattery && intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) || (stopOnLowStorage && intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) || intent.getAction().equals(Intent.ACTION_SHUTDOWN)){
41 | Intent stopintent = new Intent(context, ForegroundService.class);
42 | stopintent.setAction(ForegroundService.ACTION_STOP_SERVICE);
43 | context.startService(stopintent);
44 | }
45 |
46 | //if (intent.getAction().equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) Log.d("DeviceStatusReceiver","ACTION_AIRPLANE_MODE_CHANGED"); //for testing: Activate filter in MainActivity
47 |
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/ForegroundService.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec;
2 |
3 | import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
4 |
5 | import android.app.AlarmManager;
6 | import android.app.Notification;
7 | import android.app.NotificationChannel;
8 | import android.app.NotificationManager;
9 | import android.app.PendingIntent;
10 | import android.app.Service;
11 | import android.content.Context;
12 | import android.content.Intent;
13 |
14 | import android.content.pm.ServiceInfo;
15 | import android.net.Uri;
16 | import android.os.Build;
17 | import android.os.Build.VERSION;
18 | import android.os.Build.VERSION_CODES;
19 | import android.os.Handler;
20 | import android.os.HandlerThread;
21 | import android.os.IBinder;
22 |
23 | import android.os.Message;
24 | import android.os.PowerManager;
25 | import android.os.PowerManager.WakeLock;
26 | import android.preference.PreferenceManager;
27 | import android.util.Log;
28 |
29 | import androidx.core.app.NotificationCompat;
30 | import androidx.core.app.NotificationCompat.Builder;
31 |
32 | import java.io.File;
33 |
34 | import at.andreasrohner.spartantimelapserec.data.RecSettings;
35 | import at.andreasrohner.spartantimelapserec.recorder.Recorder;
36 |
37 | public class ForegroundService extends Service implements Handler.Callback {
38 |
39 | public static final String ACTION_STOP_SERVICE = "TimeLapse.action.STOP_SERVICE";
40 | public static boolean mIsRunning = false;
41 | private RecSettings settings;
42 | private Recorder recorder;
43 | private HandlerThread handlerThread;
44 | private WakeLock mWakeLock;
45 | private static statusListener listener;
46 | private NotificationManager mNotificationManager;
47 |
48 | public interface statusListener {
49 | void onServiceStatusChange(boolean status);
50 | }
51 |
52 | public static void registerStatusListener(statusListener l){
53 | listener = l;
54 | }
55 |
56 | @Override
57 | public IBinder onBind(Intent intent) {
58 | return null;
59 | }
60 |
61 | @Override
62 | public int onStartCommand(Intent intent, int flags, int startId) {
63 | if (intent == null || !ACTION_STOP_SERVICE.equals(intent.getAction())) {
64 | initNotif();
65 | mIsRunning = true;
66 |
67 | settings = new RecSettings();
68 | settings.load(getApplicationContext(), PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
69 |
70 | if (settings.isSchedRecEnabled() && settings.getSchedRecTime() > System.currentTimeMillis() + 10000) {
71 | AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
72 | Intent newintent = new Intent(getApplicationContext(), ScheduleReceiver.class);
73 | PendingIntent alarmIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, newintent, PendingIntent.FLAG_IMMUTABLE);
74 | alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, settings.getSchedRecTime(), alarmIntent);
75 |
76 | } else {
77 |
78 | PowerManager mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
79 | mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
80 | mWakeLock.acquire();
81 |
82 | handlerThread = new HandlerThread("recordingVideo");
83 | handlerThread.start();
84 |
85 | Context context = getApplicationContext();
86 | Handler handler = new Handler(handlerThread.getLooper(), this);
87 |
88 | recorder = Recorder.getInstance(settings, context,
89 | handler, mWakeLock);
90 |
91 | handler.post(new Runnable() {
92 | @Override
93 | public void run() {
94 | recorder.start();
95 | }
96 | });
97 | updateNotif();
98 | }
99 |
100 | if (listener!=null) listener.onServiceStatusChange(true);
101 | return START_STICKY;
102 | } else {
103 | AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
104 | intent = new Intent(this, ScheduleReceiver.class);
105 | PendingIntent alarmIntent;
106 | alarmIntent = PendingIntent.getBroadcast(getApplicationContext(),0,intent,PendingIntent.FLAG_IMMUTABLE);
107 | alarmManager.cancel(alarmIntent);
108 |
109 | stop();
110 | return START_NOT_STICKY;
111 | }
112 | }
113 |
114 | @Override
115 | public void onCreate() {
116 |
117 | }
118 |
119 | @Override
120 | public void onDestroy() {
121 | super.onDestroy();
122 | }
123 |
124 |
125 | private void stop() {
126 |
127 | File projectDir = null;
128 |
129 | if (recorder != null) {
130 | recorder.stop();
131 | projectDir = recorder.getOutputDir();
132 | recorder = null;
133 | }
134 |
135 | if (mWakeLock != null && mWakeLock.isHeld())
136 | mWakeLock.release();
137 |
138 | if (projectDir != null && projectDir.exists())
139 | sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
140 | Uri.fromFile(projectDir)));
141 |
142 | mIsRunning = false;
143 | if (listener!=null) listener.onServiceStatusChange(false);
144 | stopForeground(true);
145 | stopSelf();
146 | }
147 |
148 | private static final int NOTIF_ID = 123;
149 | private static final String CHANNEL_ID = "TimeLapseID";
150 |
151 | private void updateNotif(){
152 |
153 | Intent intent = new Intent(this, MainActivity.class);
154 | intent.setAction(Intent.ACTION_MAIN);
155 | intent.addCategory(Intent.CATEGORY_LAUNCHER);
156 | PendingIntent pi = PendingIntent.getActivity(this, NOTIF_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
157 |
158 | Notification notification = new Builder(this, CHANNEL_ID)
159 | .setSilent(true)
160 | .setOnlyAlertOnce(true)
161 | .setPriority(NotificationCompat.PRIORITY_DEFAULT) // For N and below
162 | .setContentIntent(pi)
163 | .setSmallIcon(R.drawable.ic_camera)
164 | .setAutoCancel(false)
165 | .setOngoing(true)
166 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
167 | .setContentText(getString(R.string.info_recording_running))
168 | .setContentTitle(getString(R.string.app_name)).build();
169 |
170 | mNotificationManager.notify(NOTIF_ID,notification);
171 | }
172 |
173 | private void initNotif() {
174 |
175 | mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
176 | if (VERSION.SDK_INT >= VERSION_CODES.O) {
177 | mNotificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, "TimeLapse", NotificationManager.IMPORTANCE_DEFAULT));
178 | }
179 | Intent intent = new Intent(this, MainActivity.class);
180 | intent.setAction(Intent.ACTION_MAIN);
181 | intent.addCategory(Intent.CATEGORY_LAUNCHER);
182 | PendingIntent pi = PendingIntent.getActivity(this, NOTIF_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
183 |
184 | // For N and below
185 | Notification notification = new Builder(this, CHANNEL_ID)
186 | .setSilent(true)
187 | .setOnlyAlertOnce(true)
188 | .setPriority(NotificationCompat.PRIORITY_DEFAULT) // For N and below
189 | .setContentIntent(pi)
190 | .setSmallIcon(R.drawable.ic_camera)
191 | .setAutoCancel(false)
192 | .setOngoing(true)
193 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
194 | .setContentText(getString(R.string.notification_preparing))
195 | .setContentTitle(getString(R.string.app_name)).build();
196 |
197 | if (VERSION.SDK_INT >= VERSION_CODES.Q) {
198 | startForeground(NOTIF_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA);
199 | } else {
200 | startForeground(NOTIF_ID, notification);
201 | }
202 |
203 | }
204 |
205 | @Override
206 | public boolean handleMessage(Message m) {
207 | String status = m.getData().getString("status");
208 | String tag = m.getData().getString("tag");
209 | String msg = m.getData().getString("msg");
210 |
211 | if ("error".equals(status)) {
212 | Log.e(tag, "Error: " + msg);
213 | stop();
214 | } else if ("success".equals(status)){
215 | Log.e(tag, "Success");
216 | stop();
217 | }
218 |
219 | return true;
220 | }
221 |
222 | }
223 |
224 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/GithubStar.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec;
2 |
3 | import android.content.Context;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 | import android.net.Uri;
8 | import android.preference.PreferenceManager;
9 |
10 | import androidx.appcompat.app.AlertDialog;
11 |
12 | public class GithubStar {
13 | public static void setAskForStar(boolean askForStar, Context context){
14 | SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(context);
15 | SharedPreferences.Editor editor = prefManager.edit();
16 | editor.putBoolean("askForStar", askForStar);
17 | editor.apply();
18 | }
19 |
20 | static boolean shouldShowStarDialog(Context context) {
21 | SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(context);
22 | int versionCode = prefManager.getInt("versionCode",0);
23 | boolean askForStar=prefManager.getBoolean("askForStar",true);
24 |
25 | if (prefManager.contains("versionCode") && BuildConfig.VERSION_CODE>versionCode && askForStar){ //not at first start, only after upgrade and only if use has not yet given a star or has declined
26 | SharedPreferences.Editor editor = prefManager.edit();
27 | editor.putInt("versionCode", BuildConfig.VERSION_CODE);
28 | editor.apply();
29 | return true;
30 | } else {
31 | SharedPreferences.Editor editor = prefManager.edit();
32 | editor.putInt("versionCode", BuildConfig.VERSION_CODE);
33 | editor.apply();
34 | return false;
35 | }
36 | }
37 |
38 | static void starDialog(Context context, String url){
39 | SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(context);
40 | if (prefManager.getBoolean("askForStar",true)) {
41 | AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
42 | alertDialogBuilder.setMessage(R.string.dialog_StarOnGitHub);
43 | alertDialogBuilder.setPositiveButton(context.getString(R.string.dialog_OK_button), new DialogInterface.OnClickListener() {
44 | @Override
45 | public void onClick(DialogInterface dialog, int which) {
46 | context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
47 | setAskForStar(false, context);
48 | }
49 | });
50 | alertDialogBuilder.setNegativeButton(context.getString(R.string.dialog_NO_button), new DialogInterface.OnClickListener() {
51 | @Override
52 | public void onClick(DialogInterface dialog, int which) {
53 | setAskForStar(false, context);
54 | }
55 | });
56 | alertDialogBuilder.setNeutralButton(context.getString(R.string.dialog_Later_button), null);
57 |
58 | AlertDialog alertDialog = alertDialogBuilder.create();
59 | alertDialog.show();
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec;
20 |
21 | import android.Manifest;
22 | import android.app.AlarmManager;
23 | import android.content.BroadcastReceiver;
24 | import android.content.Context;
25 | import android.content.Intent;
26 | import android.content.IntentFilter;
27 | import android.content.SharedPreferences;
28 | import android.content.pm.PackageManager;
29 | import android.net.Uri;
30 | import android.os.Build;
31 | import android.os.Bundle;
32 | import android.preference.PreferenceManager;
33 | import android.provider.Settings;
34 | import android.view.Menu;
35 | import android.view.MenuItem;
36 | import android.widget.Toast;
37 |
38 | import androidx.appcompat.app.AppCompatActivity;
39 | import androidx.core.app.ActivityCompat;
40 | import androidx.core.content.ContextCompat;
41 |
42 | import at.andreasrohner.spartantimelapserec.data.RecSettings;
43 | import at.andreasrohner.spartantimelapserec.sensor.MuteShutter;
44 |
45 | public class MainActivity extends AppCompatActivity implements ForegroundService.statusListener {
46 |
47 | private static SettingsFragment settingsFragment;
48 | private static BroadcastReceiver broadcastReceiver;
49 |
50 | @Override
51 | protected void onCreate(Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 |
54 | if (broadcastReceiver==null) broadcastReceiver = new DeviceStatusReceiver();
55 | IntentFilter filter = new IntentFilter();
56 | filter.addAction("android.intent.action.ACTION_BATTERY_LOW");
57 | filter.addAction("android.intent.action.ACTION_DEVICE_STORAGE_LOW");
58 | filter.addAction("android.intent.action.ACTION_SHUTDOWN");
59 | //filter.addAction("android.intent.action.AIRPLANE_MODE"); //for testing
60 | ContextCompat.registerReceiver(getApplicationContext(),broadcastReceiver, filter, ContextCompat.RECEIVER_EXPORTED);
61 |
62 | if (Build.VERSION.SDK_INT=Build.VERSION_CODES.TIRAMISU)) {
100 | //PERMISSION POST_NOTIFICATION is required and not tested here
101 |
102 | // Display the fragment as the main content.
103 | if (settingsFragment==null) {
104 | settingsFragment = new SettingsFragment();
105 | settingsFragment.setRetainInstance(true); //do not recreate if orientation is changed
106 | }
107 | getFragmentManager().beginTransaction()
108 | .replace(android.R.id.content, settingsFragment)
109 | .commit();
110 |
111 | } else Toast.makeText(this, getString(R.string.error_missing_permission), Toast.LENGTH_SHORT).show();
112 |
113 | AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
114 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
115 | if (!alarmManager.canScheduleExactAlarms()) {
116 | Intent intent = new Intent();
117 | intent.setAction(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
118 | startActivity(intent);
119 | }
120 | }
121 |
122 | }
123 |
124 | public void actionStart(MenuItem item) {
125 | ServiceHelper helper = new ServiceHelper(getApplicationContext());
126 | helper.start(true);
127 |
128 | invalidateOptionsMenu();
129 | }
130 |
131 | @Override
132 | protected void onDestroy() {
133 | broadcastReceiver = null;
134 | super.onDestroy();
135 | }
136 |
137 | public void actionStop(MenuItem item) {
138 | ServiceHelper helper = new ServiceHelper(getApplicationContext());
139 | helper.stop();
140 |
141 | invalidateOptionsMenu();
142 | }
143 |
144 | public void actionGallery(MenuItem item) {
145 | Intent intent = new Intent();
146 | intent.setAction(android.content.Intent.ACTION_VIEW);
147 | intent.setType("image/*");
148 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
149 | startActivity(intent);
150 | }
151 |
152 | public void actionPreview(MenuItem item) {
153 | if (!ForegroundService.mIsRunning) {
154 | Intent intent = new Intent(MainActivity.this, PreviewActivity.class);
155 | startActivity(intent);
156 | } else {
157 | Toast.makeText(this, getString(R.string.info_recording_running), Toast.LENGTH_SHORT).show();
158 | }
159 | }
160 |
161 | public void actionUnmuteAllStreams(MenuItem item) {
162 | MuteShutter mute = new MuteShutter(this);
163 | mute.maxAllStreams();
164 | }
165 |
166 | public void actionAbout(MenuItem item) {
167 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/woheller69/timelapsecamera")));
168 | }
169 |
170 | @Override
171 | public boolean onCreateOptionsMenu(Menu menu) {
172 | getMenuInflater().inflate(R.menu.main, menu);
173 | if (ForegroundService.mIsRunning){
174 | menu.findItem(R.id.action_start).setEnabled(false);
175 | menu.findItem(R.id.action_start).setIcon(ContextCompat.getDrawable(this,R.drawable.ic_radio_button_checked_disabled_24px));
176 | menu.findItem(R.id.action_preview).setEnabled(false);
177 | menu.findItem(R.id.action_preview).setIcon(ContextCompat.getDrawable(this,R.drawable.ic_visibility_disabled_24px));
178 |
179 | RecSettings settings = new RecSettings();
180 | settings.load(getApplicationContext(), PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
181 | if (settings.isSchedRecEnabled() && settings.getSchedRecTime() > System.currentTimeMillis()){
182 | menu.findItem(R.id.action_stop).setEnabled(false);
183 | menu.findItem(R.id.action_stop).setIcon(ContextCompat.getDrawable(this,R.drawable.ic_stop_circle_disabled_24px));
184 | }
185 |
186 | } else {
187 | menu.findItem(R.id.action_stop).setEnabled(false);
188 | menu.findItem(R.id.action_stop).setIcon(ContextCompat.getDrawable(this,R.drawable.ic_stop_circle_disabled_24px));
189 | }
190 | return true;
191 | }
192 |
193 | @Override
194 | public void onServiceStatusChange(boolean status) {
195 | invalidateOptionsMenu();
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/PowerSavingReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec;
20 |
21 | import android.content.BroadcastReceiver;
22 | import android.content.Context;
23 | import android.content.Intent;
24 |
25 | public class PowerSavingReceiver extends BroadcastReceiver {
26 |
27 | @Override
28 | public void onReceive(Context context, Intent intent) {
29 | Intent serviceIntent = new Intent(context, ForegroundService.class);
30 | serviceIntent.putExtra("powerSavingCallback", true);
31 | context.startService(serviceIntent);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/ScheduleReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec;
20 |
21 | import android.content.BroadcastReceiver;
22 | import android.content.Context;
23 | import android.content.Intent;
24 |
25 | public class ScheduleReceiver extends BroadcastReceiver {
26 |
27 | @Override
28 | public void onReceive(Context context, Intent intent) {
29 | Intent serviceIntent = new Intent(context, ForegroundService.class);
30 | context.startService(serviceIntent);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/ServiceHelper.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Build;
6 | import android.widget.Toast;
7 |
8 | /**
9 | * Helper class to start / stop picture service
10 | */
11 | public class ServiceHelper {
12 |
13 | /**
14 | * Context
15 | */
16 | private final Context context;
17 |
18 | /**
19 | * Constructor
20 | *
21 | * @param context Context
22 | */
23 | public ServiceHelper(Context context) {
24 | this.context = context;
25 | }
26 |
27 | /**
28 | * Start
29 | *
30 | * @param calledFromUi true if called from Activity, false if not
31 | */
32 | public void start(boolean calledFromUi) {
33 | Intent intent = new Intent(context, ForegroundService.class);
34 | if (ForegroundService.mIsRunning) {
35 | if (calledFromUi) {
36 | Toast.makeText(context, context.getString(R.string.error_already_running), Toast.LENGTH_SHORT).show();
37 | }
38 | } else {
39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
40 | context.startForegroundService(intent);
41 | } else {
42 | context.startService(intent);
43 | }
44 | }
45 | }
46 |
47 | /**
48 | * Stop
49 | */
50 | public void stop() {
51 | Intent intent = new Intent(context, ForegroundService.class);
52 | intent.setAction(ForegroundService.ACTION_STOP_SERVICE);
53 | context.startService(intent);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/SettingsFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec;
20 |
21 | import android.content.Context;
22 | import android.os.Bundle;
23 | import android.preference.PreferenceFragment;
24 |
25 | public class SettingsFragment extends PreferenceFragment {
26 | SettingsCommon settCommon;
27 |
28 | @Override
29 | public void onCreate(Bundle savedInstanceState) {
30 | Context context = getActivity().getApplicationContext();
31 |
32 | super.onCreate(savedInstanceState);
33 |
34 | addPreferencesFromResource(R.xml.preferences);
35 |
36 | settCommon = new SettingsCommon();
37 | settCommon.onCreate(context, getPreferenceScreen());
38 | }
39 |
40 | @Override
41 | public void onResume() {
42 | super.onResume();
43 | settCommon.onResume(getPreferenceScreen());
44 | }
45 |
46 | @Override
47 | public void onPause() {
48 | super.onPause();
49 | settCommon.onPause(getPreferenceScreen());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/data/RecMode.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.data;
20 |
21 | public enum RecMode {
22 | VIDEO, VIDEO_TIME_LAPSE, IMAGE_TIME_LAPSE
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/data/RecSettings.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.data;
20 |
21 | import static android.os.Environment.DIRECTORY_PICTURES;
22 |
23 |
24 | import android.content.Context;
25 | import android.content.SharedPreferences;
26 | import android.media.CamcorderProfile;
27 | import android.os.Environment;
28 | import at.andreasrohner.spartantimelapserec.preference.DateTimePreference;
29 |
30 | public class RecSettings {
31 | private int cameraId;
32 | private String projectName;
33 | private String projectPath;
34 | private RecMode recMode;
35 | private int frameRate;
36 | private int captureRate;
37 | private boolean muteShutter;
38 | private boolean stopOnLowBattery;
39 | private boolean stopOnLowStorage;
40 | private int initDelay;
41 | private int jpegQuality;
42 | private int frameWidth;
43 | private int frameHeight;
44 | private int recProfile;
45 | private long schedRecTime;
46 | private boolean schedRecEnabled;
47 | private int stopRecAfter;
48 | private int exposureCompensation;
49 | private int zoom;
50 | private int cameraInitDelay;
51 | private int cameraTriggerDelay;
52 | private boolean cameraFlash;
53 | private int videoEncodingBitRate;
54 |
55 | public static int getInteger(SharedPreferences prefs, String key, int def) {
56 | try {
57 | return Integer.parseInt(prefs.getString(key, ""));
58 | } catch (NumberFormatException e) {}
59 | return def;
60 | }
61 |
62 | public static RecMode getRecMode(SharedPreferences prefs, String key,
63 | RecMode def) {
64 | try {
65 | return RecMode.valueOf(prefs.getString(key, ""));
66 | } catch (Exception e) {}
67 | return def;
68 | }
69 |
70 | private boolean checkRecProfile(int profile) {
71 | try {
72 | if (CamcorderProfile.hasProfile(cameraId, profile)) {
73 | return true;
74 | }
75 | } catch (Exception e) {}
76 |
77 | return false;
78 | }
79 |
80 | private int selectRecVideoProfile() {
81 | if (checkRecProfile(CamcorderProfile.QUALITY_1080P)
82 | && frameHeight == 1080)
83 | return CamcorderProfile.QUALITY_1080P;
84 | if (checkRecProfile(CamcorderProfile.QUALITY_720P)
85 | && frameHeight == 720)
86 | return CamcorderProfile.QUALITY_720P;
87 | if (checkRecProfile(CamcorderProfile.QUALITY_480P)
88 | && frameHeight == 480)
89 | return CamcorderProfile.QUALITY_480P;
90 |
91 | if (checkRecProfile(CamcorderProfile.QUALITY_HIGH)
92 | && frameHeight >= 480)
93 | return CamcorderProfile.QUALITY_HIGH;
94 |
95 | if (checkRecProfile(CamcorderProfile.QUALITY_QVGA)
96 | && frameHeight == 240)
97 | return CamcorderProfile.QUALITY_QVGA;
98 |
99 | if (checkRecProfile(CamcorderProfile.QUALITY_QCIF)
100 | && frameHeight == 144)
101 | return CamcorderProfile.QUALITY_QCIF;
102 |
103 | if (checkRecProfile(CamcorderProfile.QUALITY_LOW))
104 | return CamcorderProfile.QUALITY_LOW;
105 |
106 | return CamcorderProfile.QUALITY_HIGH;
107 | }
108 |
109 | private int selectRecVideoTimeLapseProfile() {
110 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_1080P)
111 | && frameHeight == 1080)
112 | return CamcorderProfile.QUALITY_TIME_LAPSE_1080P;
113 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_720P)
114 | && frameHeight == 720)
115 | return CamcorderProfile.QUALITY_TIME_LAPSE_720P;
116 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_480P)
117 | && frameHeight == 480)
118 | return CamcorderProfile.QUALITY_TIME_LAPSE_480P;
119 |
120 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH)
121 | && frameHeight >= 480)
122 | return CamcorderProfile.QUALITY_TIME_LAPSE_HIGH;
123 |
124 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_QVGA)
125 | && frameHeight == 240)
126 | return CamcorderProfile.QUALITY_TIME_LAPSE_QVGA;
127 |
128 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_QCIF)
129 | && frameHeight == 144)
130 | return CamcorderProfile.QUALITY_TIME_LAPSE_QCIF;
131 |
132 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_LOW))
133 | return CamcorderProfile.QUALITY_TIME_LAPSE_LOW;
134 |
135 | return CamcorderProfile.QUALITY_TIME_LAPSE_HIGH;
136 | }
137 |
138 | public void load(Context context, SharedPreferences prefs) {
139 | videoEncodingBitRate = getInteger(prefs,"pref_video_encoding_br", 0);
140 | cameraId = getInteger(prefs, "pref_camera", 0);
141 | projectName = prefs.getString("pref_project_title", "");
142 | projectPath = Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES).getPath();
143 |
144 | recMode = getRecMode(prefs, "pref_rec_mode", RecMode.VIDEO_TIME_LAPSE);
145 |
146 | frameRate = getInteger(prefs, "pref_frame_rate", 30);
147 | captureRate = prefs.getInt("pref_capture_rate", 1000);
148 | muteShutter = prefs.getBoolean("pref_mute_shutter", true);
149 | stopOnLowBattery = prefs.getBoolean("pref_stop_low_battery", true);
150 | stopOnLowStorage = prefs.getBoolean("pref_stop_low_storage", true);
151 | initDelay = prefs.getInt("pref_initial_delay", 1000);
152 | jpegQuality = prefs.getInt("pref_jpeg_quality", 90);
153 | stopRecAfter = prefs.getInt("pref_stop_recording_after", 60 * 48); //two days
154 | exposureCompensation = prefs.getInt("pref_exposurecomp",0);
155 | zoom = prefs.getInt("pref_zoom",0);
156 | cameraInitDelay = prefs.getInt("pref_camera_init_delay", 500);
157 | cameraTriggerDelay = prefs.getInt("pref_camera_trigger_delay", 1000);
158 | cameraFlash = prefs.getBoolean("pref_flash",false);
159 | // negative value disables the limit
160 | if (stopRecAfter >= 47 * 60)
161 | stopRecAfter = -1;
162 | else
163 | stopRecAfter *= 60 * 1000; //convert to milli seconds
164 |
165 | String[] size = prefs.getString("pref_frame_size", "1920x1080").split("x");
166 | try {
167 | frameWidth = Integer.valueOf(size[0]);
168 | frameHeight = Integer.valueOf(size[1]);
169 | } catch (NumberFormatException e) {
170 | frameWidth = 1920;
171 | frameHeight = 1080;
172 | }
173 |
174 | if (recMode != RecMode.IMAGE_TIME_LAPSE) {
175 | if (recMode == RecMode.VIDEO_TIME_LAPSE)
176 | recProfile = selectRecVideoTimeLapseProfile();
177 | else
178 | recProfile = selectRecVideoProfile();
179 | }
180 |
181 | String schedRecValue = prefs.getString("pref_schedule_recording", null);
182 | if (schedRecValue != null) {
183 | schedRecEnabled = DateTimePreference.parseEnabled(schedRecValue);
184 | if (schedRecEnabled) {
185 | schedRecTime = DateTimePreference.parseTime(schedRecValue);
186 | }
187 | } else {
188 | schedRecEnabled = false;
189 | }
190 |
191 | }
192 |
193 | public boolean shouldUsePowerSaveMode() {
194 | if (captureRate > 10 * 1000)
195 | return true;
196 | return false;
197 | }
198 |
199 | public int getInitDelay() {
200 | return initDelay;
201 | }
202 |
203 | public void setInitDelay(int initDelay) {
204 | this.initDelay = initDelay;
205 | }
206 |
207 | public int getCameraId() {
208 | return cameraId;
209 | }
210 |
211 | public void setCameraId(int cameraId) {
212 | this.cameraId = cameraId;
213 | }
214 |
215 | public RecMode getRecMode() {
216 | return recMode;
217 | }
218 |
219 | public void setRecMode(RecMode recMode) {
220 | this.recMode = recMode;
221 | }
222 |
223 | public String getProjectName() {
224 | return projectName;
225 | }
226 |
227 | public void setProjectName(String projectName) {
228 | this.projectName = projectName;
229 | }
230 |
231 | public String getProjectPath() {
232 | return projectPath;
233 | }
234 |
235 | public void setProjectPath(String projectPath) {
236 | this.projectPath = projectPath;
237 | }
238 |
239 | public int getFrameRate() {
240 | return frameRate;
241 | }
242 |
243 | public void setFrameRate(int frameRate) {
244 | this.frameRate = frameRate;
245 | }
246 |
247 | public int getCaptureRate() {
248 | return captureRate;
249 | }
250 |
251 | public void setCaptureRate(int captureRate) {
252 | this.captureRate = captureRate;
253 | }
254 |
255 | public boolean isMuteShutter() {
256 | return muteShutter;
257 | }
258 |
259 | public void setMuteShutter(boolean muteShutter) {
260 | this.muteShutter = muteShutter;
261 | }
262 |
263 | public int getJpegQuality() {
264 | return jpegQuality;
265 | }
266 |
267 | public void setJpegQuality(int jpegQuality) {
268 | this.jpegQuality = jpegQuality;
269 | }
270 |
271 | public int getFrameWidth() {
272 | return frameWidth;
273 | }
274 |
275 | public void setFrameWidth(int frameWidth) {
276 | this.frameWidth = frameWidth;
277 | }
278 |
279 | public int getFrameHeight() {
280 | return frameHeight;
281 | }
282 |
283 | public void setFrameHeight(int frameHeight) {
284 | this.frameHeight = frameHeight;
285 | }
286 |
287 | public int getRecProfile() {
288 | return recProfile;
289 | }
290 |
291 | public void setRecProfile(int recProfile) {
292 | this.recProfile = recProfile;
293 | }
294 |
295 | public boolean isStopOnLowBattery() {
296 | return stopOnLowBattery;
297 | }
298 |
299 | public void setStopOnLowBattery(boolean stopOnLowBattery) {
300 | this.stopOnLowBattery = stopOnLowBattery;
301 | }
302 |
303 | public boolean isStopOnLowStorage() {
304 | return stopOnLowStorage;
305 | }
306 |
307 | public void setStopOnLowStorage(boolean stopOnLowStorage) {
308 | this.stopOnLowStorage = stopOnLowStorage;
309 | }
310 |
311 | public long getSchedRecTime() {
312 | return schedRecTime;
313 | }
314 |
315 | public void setSchedRecTime(long schedRecTime) {
316 | this.schedRecTime = schedRecTime;
317 | }
318 |
319 | public boolean isSchedRecEnabled() {
320 | return schedRecEnabled;
321 | }
322 |
323 | public void setSchedRecEnabled(boolean schedRecEnabled) {
324 | this.schedRecEnabled = schedRecEnabled;
325 | }
326 |
327 | public int getStopRecAfter() {
328 | return stopRecAfter;
329 | }
330 |
331 | public int getExposureCompensation() { return exposureCompensation; }
332 |
333 | public int getZoom() { return zoom;}
334 |
335 | public void setStopRecAfter(int stopRecAfter) {
336 | this.stopRecAfter = stopRecAfter;
337 | }
338 |
339 | public int getCameraInitDelay() {return cameraInitDelay;}
340 |
341 | public int getCameraTriggerDelay() {return cameraTriggerDelay;}
342 |
343 | public boolean getCameraFlash() {return cameraFlash;}
344 |
345 | public int getVideoEncodingBitRate() {return videoEncodingBitRate;}
346 | }
347 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/preference/DateTimePreference.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.preference;
20 |
21 | import java.text.DateFormat;
22 | import java.text.SimpleDateFormat;
23 | import java.util.Calendar;
24 |
25 | import android.content.Context;
26 | import android.content.res.TypedArray;
27 | import android.preference.DialogPreference;
28 | import android.util.AttributeSet;
29 | import android.view.View;
30 | import android.widget.CheckBox;
31 | import android.widget.CompoundButton;
32 | import android.widget.CompoundButton.OnCheckedChangeListener;
33 | import android.widget.DatePicker;
34 | import android.widget.TimePicker;
35 | import at.andreasrohner.spartantimelapserec.R;
36 |
37 | public class DateTimePreference extends DialogPreference implements
38 | OnCheckedChangeListener {
39 | Calendar mCal;
40 | boolean mEnabled;
41 | DatePicker mDatePicker;
42 | TimePicker mTimePicker;
43 |
44 | public DateTimePreference(Context context, AttributeSet attrs) {
45 | super(context, attrs);
46 |
47 | mCal = Calendar.getInstance();
48 |
49 | setWidgetLayoutResource(R.layout.dialog_date_widget_preference);
50 | setDialogLayoutResource(R.layout.dialog_date_preference);
51 | }
52 |
53 | public long getTimeInMillis() {
54 | if (mDatePicker != null && mTimePicker != null)
55 | mCal.set(mDatePicker.getYear(), mDatePicker.getMonth(),
56 | mDatePicker.getDayOfMonth(), mTimePicker.getCurrentHour(),
57 | mTimePicker.getCurrentMinute());
58 |
59 | if (mCal.getTimeInMillis() < System.currentTimeMillis())
60 | mCal.setTimeInMillis(System.currentTimeMillis());
61 |
62 | mCal.set(Calendar.SECOND, 0);
63 |
64 | return mCal.getTimeInMillis();
65 | }
66 |
67 | public static long parseTime(String value) {
68 | long time = System.currentTimeMillis();
69 | try {
70 | long temp;
71 | if (value != null) {
72 | String[] parts = value.split("\\|");
73 | if (parts.length == 2) {
74 | temp = Long.valueOf(parts[1]);
75 | if (temp > time)
76 | time = temp;
77 | }
78 | }
79 | } catch (NumberFormatException e) {
80 | }
81 |
82 | return time;
83 | }
84 |
85 | public static boolean parseEnabled(String value) {
86 | if (value != null && value.charAt(0) == '1')
87 | return true;
88 |
89 | return false;
90 | }
91 |
92 | public String formatDateTime() {
93 | DateFormat f = SimpleDateFormat.getDateTimeInstance(
94 | SimpleDateFormat.MEDIUM, SimpleDateFormat.SHORT);
95 |
96 | return f.format(mCal.getTime());
97 | }
98 |
99 | private void parseValue(String value) {
100 | mCal.setTimeInMillis(parseTime(value));
101 | mCal.set(Calendar.SECOND, 0);
102 | }
103 |
104 | private String createValue() {
105 | String time = (mEnabled ? "1|" : "0|");
106 | time += String.valueOf(getTimeInMillis());
107 |
108 | return time;
109 | }
110 |
111 | @Override
112 | protected void onBindView(View view) {
113 | super.onBindView(view);
114 |
115 | CheckBox checkBox = (CheckBox) view
116 | .findViewById(R.id.dialog_date_preference_enabled);
117 |
118 | String persisted = getPersistedString(null);
119 | mEnabled = parseEnabled(persisted);
120 |
121 | if (mEnabled) {
122 | if (!(mCal.getTimeInMillis() >= System.currentTimeMillis() + 10000)) mEnabled = false;
123 | }
124 | checkBox.setChecked(mEnabled);
125 | checkBox.setOnCheckedChangeListener(this);
126 | }
127 |
128 | @Override
129 | protected void onBindDialogView(View v) {
130 | super.onBindDialogView(v);
131 |
132 | parseValue(getPersistedString(null));
133 |
134 | mDatePicker = (DatePicker) v.findViewById(R.id.dialog_date_preference_date);
135 | mTimePicker = (TimePicker) v.findViewById(R.id.dialog_date_preference_time);
136 |
137 | mDatePicker.setCalendarViewShown(false);
138 | mDatePicker.setMinDate(System.currentTimeMillis() - 1000);
139 |
140 | mDatePicker.updateDate(mCal.get(Calendar.YEAR),
141 | mCal.get(Calendar.MONTH), mCal.get(Calendar.DAY_OF_MONTH));
142 |
143 | mTimePicker.setIs24HourView(true);
144 | mTimePicker.setCurrentHour(mCal.get(Calendar.HOUR_OF_DAY));
145 | mTimePicker.setCurrentMinute(mCal.get(Calendar.MINUTE));
146 | }
147 |
148 | @Override
149 | protected void onDialogClosed(boolean positiveResult) {
150 | super.onDialogClosed(positiveResult);
151 |
152 | if (positiveResult) {
153 | String time = createValue();
154 |
155 | if (callChangeListener(time)) {
156 | persistString(time);
157 | }
158 | }
159 | }
160 |
161 | @Override
162 | protected Object onGetDefaultValue(TypedArray a, int index) {
163 | return a.getString(index);
164 | }
165 |
166 | @Override
167 | protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
168 | String time = null;
169 |
170 | if (restoreValue) {
171 | time = getPersistedString((String) defaultValue);
172 | } else {
173 | time = (String) defaultValue;
174 | }
175 |
176 | parseValue(time);
177 | }
178 |
179 | @Override
180 | public void onCheckedChanged(CompoundButton check, boolean value) {
181 |
182 | mEnabled = value;
183 | if (mEnabled) {
184 | if (!(mCal.getTimeInMillis() >= System.currentTimeMillis())) {
185 | mEnabled = false;
186 | check.setChecked(false);
187 | }
188 | }
189 |
190 | String time = createValue();
191 | if (callChangeListener(time)) {
192 | persistString(time);
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/preference/EditSummaryPreference.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec.preference;
2 |
3 | import android.content.Context;
4 | import android.preference.EditTextPreference;
5 | import android.util.AttributeSet;
6 |
7 | /**
8 | * Show value in summary
9 | */
10 | public class EditSummaryPreference extends EditTextPreference {
11 |
12 | public EditSummaryPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
13 | super(context, attrs, defStyleAttr, defStyleRes);
14 | }
15 |
16 | public EditSummaryPreference(Context context, AttributeSet attrs, int defStyleAttr) {
17 | super(context, attrs, defStyleAttr);
18 | }
19 |
20 | public EditSummaryPreference(Context context, AttributeSet attrs) {
21 | super(context, attrs);
22 | }
23 |
24 | public EditSummaryPreference(Context context) {
25 | super(context);
26 | }
27 |
28 | @Override
29 | public CharSequence getSummary() {
30 | return getText();
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/preference/IntervalPickerPreference.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec.preference;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.preference.DialogPreference;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 | import android.widget.NumberPicker;
9 |
10 | import at.andreasrohner.spartantimelapserec.R;
11 |
12 | public class IntervalPickerPreference extends DialogPreference {
13 | private static final int DEFAULT_VALUE = 0;
14 |
15 | private int mValue = DEFAULT_VALUE;
16 | private NumberPicker mMilliPicker;
17 | private NumberPicker mSecondsPicker;
18 | private NumberPicker mMinutesPicker;
19 |
20 | public IntervalPickerPreference(Context context, AttributeSet attrs) {
21 |
22 | super(context, attrs);
23 |
24 | setDialogLayoutResource(R.layout.dialog_intervalpicker_preference);
25 | }
26 |
27 |
28 |
29 | @Override
30 | protected void onBindDialogView(View v) {
31 | super.onBindDialogView(v);
32 |
33 | mValue = getPersistedInt(DEFAULT_VALUE);
34 |
35 | mMilliPicker = (NumberPicker) v.findViewById(R.id.dialog_seekbar_preference_milli);
36 | String[] nums = new String[100];
37 | for(int i=0; i.
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.preference;
20 |
21 | import android.content.Context;
22 | import android.content.DialogInterface;
23 | import android.preference.EditTextPreference;
24 | import android.util.AttributeSet;
25 | import android.view.View;
26 | import android.view.inputmethod.InputMethodManager;
27 |
28 | public class NoKBEditTextPreference extends EditTextPreference {
29 | private View mView;
30 |
31 | @Override
32 | protected void onBindView(View view) {
33 | mView = view;
34 | super.onBindView(view);
35 | }
36 |
37 | public NoKBEditTextPreference(Context context) {
38 | super(context);
39 | }
40 |
41 | public NoKBEditTextPreference(Context context, AttributeSet attrs,
42 | int defStyle) {
43 | super(context, attrs, defStyle);
44 | }
45 |
46 | public NoKBEditTextPreference(Context context, AttributeSet attrs) {
47 | super(context, attrs);
48 | }
49 |
50 | @Override
51 | public void onDismiss(DialogInterface dialog) {
52 | super.onDismiss(dialog);
53 |
54 | InputMethodManager imm = (InputMethodManager) getContext()
55 | .getSystemService(Context.INPUT_METHOD_SERVICE);
56 | imm.hideSoftInputFromWindow(mView.getWindowToken(),
57 | InputMethodManager.HIDE_NOT_ALWAYS);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/preference/SeekBarPreference.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.preference;
20 |
21 | import android.content.Context;
22 | import android.content.res.TypedArray;
23 | import android.preference.DialogPreference;
24 | import android.util.AttributeSet;
25 | import android.view.View;
26 | import android.widget.SeekBar;
27 | import android.widget.TextView;
28 | import at.andreasrohner.spartantimelapserec.R;
29 |
30 | public class SeekBarPreference extends DialogPreference implements
31 | SeekBar.OnSeekBarChangeListener {
32 | private static final int DEFAULT_VALUE = 0;
33 |
34 | private TextView mValueDisp;
35 | private SeekBar mSeekBar;
36 | private String mSuffix;
37 | private int mMaxValue, mMinValue, mValue = DEFAULT_VALUE;
38 | private OnFormatOutputValueListener mFormatListener;
39 | private float mLog;
40 | private int mStickyValue;
41 |
42 | public SeekBarPreference(Context context, AttributeSet attrs) {
43 |
44 | super(context, attrs);
45 |
46 | TypedArray a = context.obtainStyledAttributes(attrs,
47 | R.styleable.TimeLapse);
48 |
49 | int resId = a.getResourceId(R.styleable.TimeLapse_suffix,0);
50 | if (resId == 0)
51 | mSuffix = a.getString(R.styleable.TimeLapse_suffix);
52 | else
53 | mSuffix = context.getString(resId);
54 |
55 | mMaxValue = a.getInteger(R.styleable.TimeLapse_max, 100);
56 | mMinValue = a.getInteger(R.styleable.TimeLapse_min, 0);
57 | mLog = a.getFloat(R.styleable.TimeLapse_log, 0);
58 | mStickyValue = a.getInteger(R.styleable.TimeLapse_stickyValue, 0);
59 | a.recycle();
60 | setDialogLayoutResource(R.layout.dialog_seekbar_preference);
61 | }
62 |
63 | public interface OnFormatOutputValueListener {
64 | String onFormatOutputValue(int value, String suffix);
65 | }
66 |
67 | public void setOnFormatOutputValueListener(
68 | OnFormatOutputValueListener listener) {
69 | mFormatListener = listener;
70 | }
71 |
72 | public void setMaxValue(int value) {
73 | mMaxValue = value;
74 | if (mSeekBar != null)
75 | mSeekBar.setMax(mMaxValue - mMinValue);
76 | }
77 |
78 | public void setMinValue(int value) {
79 | mMinValue = value;
80 | }
81 |
82 | private int getLogValue(int val) {
83 | if (mLog > 1)
84 | return (int) Math.round(((Math.log(val) / Math.log(mLog)) * 1000));
85 | return val;
86 | }
87 |
88 | private int getPowValue(int val) {
89 | if (mLog > 1) {
90 | val = (int) Math.round(Math.pow(mLog, ((double) val) / 1000));
91 | if (mStickyValue > 0) {
92 | int mod = val % mStickyValue;
93 | int area = 15 * mStickyValue / 100;
94 | if (val > mStickyValue) {
95 | if (mod < area || val > mStickyValue * 8)
96 | val -= mod;
97 | else if (mod > mStickyValue - area)
98 | val = val - mod + mStickyValue;
99 | }
100 | }
101 | }
102 | return val;
103 | }
104 |
105 | @Override
106 | protected void onBindDialogView(View v) {
107 | super.onBindDialogView(v);
108 |
109 | mValue = getPersistedInt(DEFAULT_VALUE);
110 |
111 | mValueDisp = (TextView) v
112 | .findViewById(R.id.dialog_seekbar_preference_value);
113 |
114 | mSeekBar = (SeekBar) v
115 | .findViewById(R.id.dialog_seekbar_preference_control);
116 | mSeekBar.setMax(getLogValue(mMaxValue - mMinValue));
117 | mSeekBar.setOnSeekBarChangeListener(this);
118 | mSeekBar.setProgress(getLogValue(mValue - mMinValue));
119 | mValueDisp.setText(formatValue(mValue));
120 | }
121 |
122 | @Override
123 | protected void onDialogClosed(boolean positiveResult) {
124 | if (positiveResult) {
125 | persistInt(mValue);
126 | }
127 | }
128 |
129 | @Override
130 | protected void onSetInitialValue(boolean restorePersistedValue,
131 | Object defaultValue) {
132 | if (restorePersistedValue) {
133 | // Restore existing state
134 | mValue = getPersistedInt(DEFAULT_VALUE);
135 | } else {
136 | // Set default state from the XML attribute
137 | mValue = (Integer) defaultValue;
138 | persistInt(mValue);
139 | }
140 | }
141 |
142 | @Override
143 | protected Object onGetDefaultValue(TypedArray a, int index) {
144 | return a.getInteger(index, DEFAULT_VALUE);
145 | }
146 |
147 | @Override
148 | public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
149 | value = getPowValue(value);
150 | value += mMinValue;
151 | mValue = value;
152 |
153 | String text = formatValue(value);
154 | if (mValueDisp != null)
155 | mValueDisp.setText(text);
156 | }
157 |
158 | private String formatValue(int value) {
159 | String text;
160 | if (mFormatListener != null) {
161 | text = mFormatListener.onFormatOutputValue(value, mSuffix);
162 | } else {
163 | text = String.valueOf(value);
164 | if (mSuffix != null)
165 | text += " " + mSuffix;
166 | }
167 | return text;
168 | }
169 |
170 | public int getmValue(){return mValue;}
171 |
172 | @Override
173 | public void onStartTrackingTouch(SeekBar arg0) {
174 | }
175 |
176 | @Override
177 | public void onStopTrackingTouch(SeekBar arg0) {
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/ImageRecorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.recorder;
20 |
21 | import java.io.File;
22 | import java.io.FileOutputStream;
23 | import java.io.IOException;
24 | import java.util.HashSet;
25 | import java.util.List;
26 | import java.util.Set;
27 |
28 | import android.content.Context;
29 | import android.graphics.ImageFormat;
30 | import android.graphics.SurfaceTexture;
31 | import android.hardware.Camera;
32 | import android.hardware.Camera.AutoFocusCallback;
33 | import android.hardware.Camera.ErrorCallback;
34 | import android.os.Handler;
35 | import android.os.Looper;
36 | import android.os.SystemClock;
37 | import android.util.Log;
38 |
39 | import at.andreasrohner.spartantimelapserec.data.RecSettings;
40 |
41 | public class ImageRecorder extends Recorder implements Runnable,
42 | Camera.PictureCallback, ErrorCallback, AutoFocusCallback {
43 | private static final int CONTINUOUS_CAPTURE_THRESHOLD = 3000;
44 | private static final int RELEASE_CAMERA_THRESHOLD = 2000;
45 | protected long mEndTime;
46 | protected long mStartPreviewTime;
47 | protected boolean mUseAutoFocus;
48 | protected Camera.PictureCallback pictureCallback;
49 | protected AutoFocusCallback autoFocusCallback;
50 | protected boolean mWaitCamReady;
51 |
52 | /**
53 | * Current / last recorded image
54 | */
55 | private static File currentRecordedImage;
56 |
57 | /**
58 | * Count of recorded images within the whole app session
59 | */
60 | private static int recordedImagesCount = 0;
61 |
62 | public ImageRecorder(RecSettings settings,
63 | Context context, Handler handler) {
64 | super(settings, context, handler);
65 |
66 | if (settings.getStopRecAfter() > 0)
67 | mEndTime = System.currentTimeMillis() + settings.getInitDelay()
68 | + settings.getStopRecAfter();
69 |
70 | if (mCanDisableShutterSound)
71 | mMute = null;
72 |
73 | pictureCallback=this;
74 | autoFocusCallback=this;
75 | }
76 |
77 | /**
78 | * @return Current / last recorded image
79 | */
80 | public static File getCurrentRecordedImage() {
81 | return currentRecordedImage;
82 | }
83 |
84 | /**
85 | * @return Count of recorded images within the whole app session
86 | */
87 | public static int getRecordedImagesCount() {
88 | return recordedImagesCount;
89 | }
90 |
91 | @Override
92 | public void stop() {
93 | if (mHandler != null)
94 | mHandler.removeCallbacks(this);
95 |
96 | muteShutter();
97 |
98 | super.stop();
99 | }
100 |
101 | protected void scheduleNextPicture() {
102 | long diffTime = SystemClock.elapsedRealtime() - mStartPreviewTime;
103 | long delay = mSettings.getCaptureRate() - diffTime;
104 | if (delay >= RELEASE_CAMERA_THRESHOLD && mSettings.getCaptureRate() >= CONTINUOUS_CAPTURE_THRESHOLD){
105 | releaseCamera();
106 | }
107 |
108 | if (delay <= 0)
109 | mHandler.post(this);
110 | else
111 | mHandler.postDelayed(this, delay);
112 | }
113 |
114 | @Override
115 | public void onPictureTaken(byte[] data, Camera camera) {
116 | try {
117 | File file = getOutputFile("jpg");
118 | currentRecordedImage = file;
119 | FileOutputStream out = new FileOutputStream(file);
120 | out.write(data);
121 | out.close();
122 | mWaitCamReady = false;
123 | recordedImagesCount++;
124 | scheduleNextPicture();
125 | } catch (Exception e) {
126 | handleError(getClass().getSimpleName(), e.getMessage());
127 | }
128 | }
129 |
130 | @Override
131 | public void onAutoFocus(boolean success, Camera camera) {
132 | try {
133 | muteShutter();
134 | new Handler(Looper.getMainLooper()).postDelayed(() -> {
135 | if (mCamera!=null) {
136 | camera.takePicture(null, null, pictureCallback);
137 | }
138 | }, mWaitCamReady ? mSettings.getCameraTriggerDelay() : 0);
139 |
140 | } catch (Exception e) {
141 | e.printStackTrace();
142 | handleError(getClass().getSimpleName(), e.getMessage());
143 | }
144 | }
145 |
146 | @Override
147 | public void run() {
148 | try {
149 | if (mEndTime > 0 && mEndTime < System.currentTimeMillis()) {
150 | success(); //tell service to stop
151 | return;
152 | }
153 |
154 | mStartPreviewTime = SystemClock.elapsedRealtime();
155 |
156 | if (mCamera == null)
157 | prepareRecord();
158 |
159 | Log.d("Camera","Wait:"+ mWaitCamReady);
160 |
161 | new Handler(Looper.getMainLooper()).postDelayed(() -> {
162 | if (mCamera!=null) {
163 | mCamera.startPreview();
164 | if (mUseAutoFocus) {
165 | mCamera.autoFocus(autoFocusCallback);
166 | }
167 | else {
168 | onAutoFocus(true, mCamera);
169 | }
170 | }
171 | }, mWaitCamReady ? mSettings.getCameraInitDelay() : 0);
172 |
173 | } catch (Exception e) {
174 | Log.e("Error","startPreview");
175 | e.printStackTrace();
176 | handleError(getClass().getSimpleName(), e.getMessage());
177 | }
178 | }
179 |
180 | protected void setWhiteBalance(Camera.Parameters params,
181 | Set suppModes) {
182 | if (suppModes.contains(Camera.Parameters.WHITE_BALANCE_AUTO)) {
183 | params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
184 | }
185 | }
186 |
187 | protected void setFocusMode(Camera.Parameters params, Set suppModes) {
188 | if (mSettings.getCaptureRate() < CONTINUOUS_CAPTURE_THRESHOLD && suppModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
189 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
190 | } else if (suppModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)
191 | && mSettings.getCaptureRate() < CONTINUOUS_CAPTURE_THRESHOLD) {
192 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
193 | } else if (suppModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
194 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
195 | mUseAutoFocus = true;
196 | }
197 | }
198 |
199 | protected void setCameraParams() throws IOException {
200 | Camera.Parameters params = mCamera.getParameters();
201 |
202 | /*
203 | * params.set("cam_mode", 1); hack is not necessary for pictures
204 | */
205 |
206 | List suppList = params.getSupportedWhiteBalance();
207 | if (suppList != null) {
208 | Set suppModes = new HashSet();
209 | suppModes.addAll(suppList);
210 |
211 | setWhiteBalance(params, suppModes);
212 | }
213 |
214 | suppList = params.getSupportedFocusModes();
215 | if (suppList != null) {
216 | Set suppModes = new HashSet();
217 | suppModes.addAll(suppList);
218 |
219 | setFocusMode(params, suppModes);
220 | }
221 |
222 | params.setPictureFormat(ImageFormat.JPEG);
223 |
224 | params.setPictureSize(mSettings.getFrameWidth(),
225 | mSettings.getFrameHeight());
226 |
227 | params.setJpegQuality(mSettings.getJpegQuality());
228 | params.setRotation(getCameraRotation(mSettings.getCameraId()));
229 | params.setExposureCompensation(mSettings.getExposureCompensation());
230 | params.setZoom(mSettings.getZoom());
231 | params.setFlashMode(mSettings.getCameraFlash() ? Camera.Parameters.FLASH_MODE_ON : Camera.Parameters.FLASH_MODE_OFF);
232 | mCamera.setParameters(params);
233 |
234 | mCamera.setErrorCallback(this);
235 | }
236 |
237 | protected void prepareRecord() throws IOException {
238 | mWaitCamReady = mCamera == null;
239 | releaseCamera();
240 |
241 | mCamera = Camera.open(mSettings.getCameraId());
242 |
243 | setCameraParams();
244 |
245 | SurfaceTexture surfaceTexture = new SurfaceTexture(10);
246 | mCamera.setPreviewTexture(surfaceTexture);
247 |
248 | }
249 |
250 | protected void doRecord() {
251 | run();
252 | }
253 |
254 | @Override
255 | public void onError(int error, Camera camera) {
256 | switch (error) {
257 | case Camera.CAMERA_ERROR_SERVER_DIED:
258 | handleError(getClass().getSimpleName(), "Cameraserver died");
259 | break;
260 | default:
261 | handleError(getClass().getSimpleName(),
262 | "Unkown error occured while recording");
263 | break;
264 | }
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/PowerSavingImageRecorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.recorder;
20 |
21 | import android.app.AlarmManager;
22 | import android.app.PendingIntent;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.os.Handler;
26 | import android.os.PowerManager.WakeLock;
27 | import android.os.SystemClock;
28 | import at.andreasrohner.spartantimelapserec.MainActivity;
29 | import at.andreasrohner.spartantimelapserec.PowerSavingReceiver;
30 | import at.andreasrohner.spartantimelapserec.data.RecSettings;
31 |
32 | public class PowerSavingImageRecorder extends ImageRecorder {
33 | private WakeLock mWakeLock;
34 | private AlarmManager mAlarmMgr;
35 |
36 | public PowerSavingImageRecorder(RecSettings settings,
37 | Context context, Handler handler,
38 | WakeLock wakeLock) {
39 | super(settings, context, handler);
40 |
41 | this.mWakeLock = wakeLock;
42 |
43 | mAlarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
44 | }
45 |
46 | @Override
47 | public void stop() {
48 | if (mContext != null && mAlarmMgr != null) {
49 | Intent intent = new Intent(mContext, PowerSavingReceiver.class);
50 | PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0,
51 | intent, PendingIntent.FLAG_IMMUTABLE);
52 | mAlarmMgr.cancel(alarmIntent);
53 | mAlarmMgr = null;
54 | }
55 |
56 | super.stop();
57 | }
58 |
59 | @Override
60 | protected void scheduleNextPicture() {
61 | if (mContext != null && mAlarmMgr != null) {
62 | Intent intent = new Intent(mContext, PowerSavingReceiver.class);
63 | PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
64 |
65 | Intent intent2 = new Intent(mContext, MainActivity.class);
66 | intent2.setAction(Intent.ACTION_MAIN);
67 | intent2.addCategory(Intent.CATEGORY_LAUNCHER);
68 | PendingIntent openIntent = PendingIntent.getActivity(mContext, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
69 |
70 | long diffTime = SystemClock.elapsedRealtime() - mStartPreviewTime;
71 | long delay = mSettings.getCaptureRate() - diffTime;
72 | if (delay < 0)
73 | delay = 0;
74 |
75 | mAlarmMgr.setAlarmClock(new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + delay, openIntent), alarmIntent);
76 | }
77 |
78 | disableOrientationSensor();
79 | releaseCamera();
80 | unmuteShutter();
81 |
82 | mWakeLock.release();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/Recorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.recorder;
20 |
21 | import java.io.File;
22 | import java.io.IOException;
23 |
24 | import android.content.Context;
25 | import android.hardware.Camera;
26 | import android.hardware.Camera.CameraInfo;
27 | import android.os.Bundle;
28 | import android.os.Handler;
29 | import android.os.Message;
30 | import android.os.SystemClock;
31 | import android.os.PowerManager.WakeLock;
32 | import android.text.format.DateFormat;
33 | import android.util.Log;
34 | import at.andreasrohner.spartantimelapserec.data.RecSettings;
35 | import at.andreasrohner.spartantimelapserec.sensor.MuteShutter;
36 | import at.andreasrohner.spartantimelapserec.sensor.OrientationSensor;
37 |
38 | public abstract class Recorder {
39 | protected Context mContext;
40 | protected RecSettings mSettings;
41 | protected Camera mCamera;
42 | protected boolean mCanDisableShutterSound;
43 | protected Handler mHandler;
44 | protected int mInitDelay;
45 | private OrientationSensor mOrientation;
46 | protected MuteShutter mMute;
47 | private File mOutputDir;
48 | private int mFileIndex;
49 |
50 | public static Recorder getInstance(RecSettings settings,
51 | Context context, Handler handler,
52 | WakeLock wakeLock) {
53 | Recorder recorder;
54 |
55 | switch (settings.getRecMode()) {
56 | case VIDEO_TIME_LAPSE:
57 | recorder = new VideoTimeLapseRecorder(settings,
58 | context, handler);
59 | break;
60 | case IMAGE_TIME_LAPSE:
61 | if (settings.shouldUsePowerSaveMode()) {
62 | recorder = new PowerSavingImageRecorder(settings,
63 | context, handler, wakeLock);
64 | } else {
65 | recorder = new ImageRecorder(settings, context,
66 | handler);
67 | }
68 | break;
69 | default:
70 | recorder = new VideoRecorder(settings, context,
71 | handler);
72 | break;
73 | }
74 |
75 | return recorder;
76 | }
77 |
78 | public Recorder(RecSettings settings, Context context, Handler handler) {
79 | mContext = context;
80 |
81 | mOrientation = new OrientationSensor(context);
82 | mOrientation.enable();
83 |
84 | mSettings = settings;
85 | mHandler = handler;
86 |
87 | CameraInfo info = new CameraInfo();
88 | Camera.getCameraInfo(mSettings.getCameraId(), info);
89 | mCanDisableShutterSound = info.canDisableShutterSound;
90 |
91 | mMute = new MuteShutter(context);
92 | mOutputDir = new File(settings.getProjectPath() + "/"
93 | + settings.getProjectName() + "/"
94 | + DateFormat.format("yyyy-MM-dd", System.currentTimeMillis())
95 | + "/");
96 |
97 | if (!mOutputDir.exists() && !mOutputDir.mkdirs()) {
98 | Log.e("TimeLapseCamera", "Failed to make directory: " + mOutputDir.toString());
99 | return;
100 | }
101 |
102 | mInitDelay = settings.getInitDelay();
103 | }
104 |
105 | protected void releaseCamera() {
106 | if (mCamera == null)
107 | return;
108 |
109 | try {
110 | mCamera.reconnect();
111 | mCamera.release();
112 | } catch (Exception e) {
113 | e.printStackTrace();
114 | }
115 | mCamera = null;
116 | }
117 |
118 | protected void handleError(String tag, String msg) {
119 | if (mHandler != null) {
120 | Message m = new Message();
121 | Bundle b = new Bundle();
122 | b.putString("status", "error");
123 | b.putString("tag", tag);
124 | b.putString("msg", msg);
125 | m.setData(b);
126 | m.setTarget(mHandler);
127 | mHandler.sendMessage(m);
128 | mHandler = null;
129 | }
130 | }
131 |
132 | protected void success() {
133 | if (mHandler != null) {
134 | Message m = new Message();
135 | Bundle b = new Bundle();
136 | b.putString("status", "success");
137 | m.setData(b);
138 | m.setTarget(mHandler);
139 | mHandler.sendMessage(m);
140 | }
141 | }
142 |
143 | protected void disableOrientationSensor() {
144 | if (mOrientation != null)
145 | mOrientation.disable();
146 | }
147 |
148 | protected void enableOrientationSensor() {
149 | if (mOrientation != null)
150 | mOrientation.enable();
151 | }
152 |
153 | public void stop() {
154 | disableOrientationSensor();
155 | mOrientation = null;
156 |
157 | releaseCamera();
158 | unmuteShutter();
159 |
160 | mHandler = null;
161 | mContext = null;
162 | mMute = null;
163 | }
164 |
165 | protected abstract void prepareRecord() throws Exception;
166 |
167 | protected abstract void doRecord() throws Exception;
168 |
169 | public void start() {
170 | long timeDiff = SystemClock.elapsedRealtime();
171 | Runnable r = new Runnable() {
172 | @Override
173 | public void run() {
174 | try {
175 | doRecord();
176 | } catch (Exception e) {
177 | e.printStackTrace();
178 | handleError(Recorder.class.getSimpleName(), e.getMessage());
179 | }
180 | }
181 | };
182 |
183 | if (mHandler == null || mContext == null)
184 | return;
185 |
186 | enableOrientationSensor();
187 |
188 | try {
189 | prepareRecord();
190 | } catch (Exception e) {
191 | e.printStackTrace();
192 | handleError(getClass().getSimpleName(), e.getMessage());
193 | return;
194 | }
195 |
196 | timeDiff = SystemClock.elapsedRealtime() - timeDiff;
197 | timeDiff = mInitDelay - timeDiff;
198 | if (timeDiff <= 0)
199 | mHandler.post(r);
200 | else
201 | mHandler.postDelayed(r, timeDiff);
202 |
203 | mInitDelay = 0;
204 | }
205 |
206 | public File getOutputDir() {
207 | return mOutputDir;
208 | }
209 |
210 | protected File getOutputFile(String ext) throws IOException {
211 | File outFile;
212 | do {
213 | outFile = new File(mOutputDir, mSettings.getProjectName()
214 | + mFileIndex + "." + ext);
215 | mFileIndex++;
216 | } while (outFile.isFile());
217 |
218 | if (!mOutputDir.isDirectory())
219 | throw new IOException("Could not open directory");
220 |
221 | return outFile;
222 | }
223 |
224 | protected int getCameraRotation(int cameraId) {
225 | if (mOrientation != null)
226 | return mOrientation.getCameraRotation(cameraId);
227 | return 0;
228 | }
229 |
230 |
231 | protected void muteShutter() {
232 | if (mSettings != null && mSettings.isMuteShutter()) {
233 | if (mCanDisableShutterSound) {
234 | // don't merge with upper if (to prevent elseif-branch if
235 | // mCamera == null)
236 | if (mCamera != null)
237 | mCamera.enableShutterSound(false);
238 | } else if (mMute != null) {
239 | mMute.muteShutter();
240 | }
241 | }
242 |
243 | }
244 |
245 | protected void unmuteShutter() {
246 | if (mSettings != null && mSettings.isMuteShutter()) {
247 | if (mCanDisableShutterSound) {
248 | // don't merge with upper if (to prevent elseif-branch if
249 | // mCamera == null)
250 | if (mCamera != null)
251 | mCamera.enableShutterSound(true);
252 | } else if (mMute != null) {
253 | mMute.unmuteShutter();
254 | }
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/VideoRecorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.recorder;
20 |
21 | import java.io.IOException;
22 | import java.util.HashSet;
23 | import java.util.List;
24 | import java.util.Set;
25 |
26 | import android.content.Context;
27 | import android.graphics.SurfaceTexture;
28 | import android.hardware.Camera;
29 | import android.media.CamcorderProfile;
30 | import android.media.MediaRecorder;
31 | import android.media.MediaRecorder.OnErrorListener;
32 | import android.media.MediaRecorder.OnInfoListener;
33 | import android.os.Handler;
34 | import android.util.Log;
35 | import at.andreasrohner.spartantimelapserec.data.RecSettings;
36 |
37 | public class VideoRecorder extends Recorder implements OnInfoListener,
38 | OnErrorListener {
39 | protected MediaRecorder mMediaRecorder;
40 | protected int mRate;
41 |
42 | public VideoRecorder(RecSettings settings,
43 | Context context, Handler handler) {
44 | super(settings, context, handler);
45 | }
46 |
47 | protected int getFrameRate() {
48 | Camera.Parameters params = mCamera.getParameters();
49 |
50 | List ranges = params.getSupportedPreviewFpsRange();
51 | if (ranges == null)
52 | return -1;
53 |
54 | int fps = mSettings.getFrameRate() * 1000;
55 | int selFps = ranges.get(ranges.size() - 1)[1];
56 |
57 | for (int i = 0; i < ranges.size(); ++i) {
58 | int[] range = ranges.get(i);
59 |
60 | if (fps >= range[0] && fps <= range[1]) {
61 | selFps = fps / 1000;
62 | break;
63 | }
64 | }
65 |
66 | return selFps;
67 | }
68 |
69 | private void releaseMediaRecorder() {
70 | if (mMediaRecorder == null)
71 | return;
72 |
73 | try {
74 | mMediaRecorder.stop();
75 | mMediaRecorder.reset();
76 | mMediaRecorder.release();
77 | } catch (Exception e) {
78 | e.printStackTrace();
79 | }
80 | mMediaRecorder = null;
81 | }
82 |
83 | @Override
84 | public void stop() {
85 | muteShutter();
86 | releaseMediaRecorder();
87 |
88 | super.stop();
89 | }
90 |
91 | protected void setCameraParams() throws IOException {
92 | Camera.Parameters params = mCamera.getParameters();
93 | double ratio = (double) mSettings.getFrameWidth()
94 | / mSettings.getFrameHeight();
95 | /*
96 | * hack necessary for samsung phones to enable 16:9 recordings otherwise
97 | * the camera will record 4:3 image and stretch it to 16:9
98 | */
99 | if (Math.abs(ratio - (16D / 9D)) < 0.01)
100 | params.set("cam_mode", 1);
101 |
102 | List suppList = params.getSupportedWhiteBalance();
103 | if (suppList != null) {
104 | Set suppModes = new HashSet();
105 | suppModes.addAll(suppList);
106 |
107 | if (suppModes.contains(Camera.Parameters.WHITE_BALANCE_AUTO)) {
108 | params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
109 | }
110 | }
111 |
112 | suppList = params.getSupportedFocusModes();
113 | if (suppList != null) {
114 | Set suppModes = new HashSet();
115 | suppModes.addAll(suppList);
116 |
117 | if (suppModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
118 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
119 | }
120 | }
121 | params.setExposureCompensation(mSettings.getExposureCompensation());
122 | params.setZoom(mSettings.getZoom());
123 | mCamera.setParameters(params);
124 | }
125 |
126 | @Override
127 | protected void prepareRecord() throws IOException {
128 | releaseMediaRecorder();
129 |
130 | releaseCamera();
131 |
132 | mCamera = Camera.open(mSettings.getCameraId());
133 |
134 | setCameraParams();
135 |
136 | mRate = getFrameRate();
137 |
138 | SurfaceTexture surfaceTexture = new SurfaceTexture(10);
139 | mCamera.setPreviewTexture(surfaceTexture);
140 | //mCamera.setPreviewDisplay(mSurfaceHolder);
141 |
142 | muteShutter();
143 |
144 | mCamera.unlock();
145 |
146 | mMediaRecorder = new MediaRecorder();
147 | mMediaRecorder.setCamera(mCamera);
148 | }
149 |
150 | @Override
151 | protected void doRecord() throws IllegalStateException, IOException {
152 | mMediaRecorder.setOrientationHint(getCameraRotation(mSettings.getCameraId()));
153 | // no need for more sensor data
154 | disableOrientationSensor();
155 |
156 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
157 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
158 |
159 | CamcorderProfile p = CamcorderProfile.get(mSettings.getCameraId(),
160 | mSettings.getRecProfile());
161 | p.videoFrameWidth = mSettings.getFrameWidth();
162 | p.videoFrameHeight = mSettings.getFrameHeight();
163 | mMediaRecorder.setProfile(p);
164 |
165 | if (mRate != -1)
166 | mMediaRecorder.setVideoFrameRate(mRate);
167 | mMediaRecorder.setOutputFile(getOutputFile("mp4").getAbsolutePath());
168 | mMediaRecorder.setVideoSize(mSettings.getFrameWidth(), mSettings.getFrameHeight());
169 |
170 | if (mSettings.getStopRecAfter() > 0) {
171 | mMediaRecorder.setMaxDuration(mSettings.getStopRecAfter());
172 | mMediaRecorder.setOnInfoListener(this);
173 | }
174 |
175 | Log.i(getClass().getSimpleName(), "Starting video recording");
176 | mMediaRecorder.setOnErrorListener(this);
177 | if (mSettings.getVideoEncodingBitRate()>0) mMediaRecorder.setVideoEncodingBitRate(mSettings.getVideoEncodingBitRate());
178 | mMediaRecorder.prepare();
179 |
180 | mMediaRecorder.start();
181 | }
182 |
183 | @Override
184 | protected void muteShutter() {
185 | // mCamera.enableShutterSound(false); does not work for MediaRecorder
186 | // (on Samsung Galaxy S3)
187 | if (mSettings != null && mSettings.isMuteShutter() && mMute != null)
188 | mMute.muteShutter();
189 | }
190 |
191 | @Override
192 | protected void unmuteShutter() {
193 | // mCamera.enableShutterSound(true); does not work for MediaRecorder (on
194 | // Samsung Galaxy S3)
195 | if (mSettings != null && mSettings.isMuteShutter() && mMute != null)
196 | mMute.unmuteShutter();
197 | }
198 |
199 | @Override
200 | public void onInfo(MediaRecorder mr, int what, int extra) {
201 | switch (what) {
202 | case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
203 | success(); //tell service to stop
204 | break;
205 | }
206 | }
207 |
208 | @Override
209 | public void onError(MediaRecorder mr, int what, int extra) {
210 | switch (what) {
211 | case MediaRecorder.MEDIA_ERROR_SERVER_DIED:
212 | handleError(getClass().getSimpleName(), "Mediaserver died");
213 | break;
214 | default:
215 | handleError(getClass().getSimpleName(),
216 | "Unkown error occured while recording");
217 | break;
218 | }
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/VideoTimeLapseRecorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.recorder;
20 |
21 | import java.io.IOException;
22 |
23 | import android.content.Context;
24 | import android.media.CamcorderProfile;
25 | import android.media.MediaRecorder;
26 | import android.os.Handler;
27 | import android.util.Log;
28 | import android.widget.Toast;
29 | import at.andreasrohner.spartantimelapserec.R;
30 | import at.andreasrohner.spartantimelapserec.data.RecSettings;
31 |
32 | public class VideoTimeLapseRecorder extends VideoRecorder {
33 |
34 | public VideoTimeLapseRecorder(RecSettings settings,
35 | Context context, Handler handler) {
36 | super(settings, context, handler);
37 | }
38 |
39 | protected void doRecord() throws IllegalStateException, IOException {
40 | mMediaRecorder.setOrientationHint(getCameraRotation(mSettings
41 | .getCameraId()));
42 | // no need for more sensor data
43 | disableOrientationSensor();
44 |
45 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
46 |
47 | CamcorderProfile p = CamcorderProfile.get(mSettings.getCameraId(),
48 | mSettings.getRecProfile());
49 | p.videoFrameWidth = mSettings.getFrameWidth();
50 | p.videoFrameHeight = mSettings.getFrameHeight();
51 | mMediaRecorder.setProfile(p);
52 |
53 | mMediaRecorder.setCaptureRate(1000 / ((double) mSettings.getCaptureRate()));
54 |
55 | if (mRate != -1)
56 | mMediaRecorder.setVideoFrameRate(mRate);
57 | mMediaRecorder.setOutputFile(getOutputFile("mp4").getAbsolutePath());
58 | mMediaRecorder.setVideoSize(mSettings.getFrameWidth(),
59 | mSettings.getFrameHeight());
60 |
61 | if (mSettings.getStopRecAfter() > 0) {
62 | if (mRate != -1) {
63 | mRate = CamcorderProfile.get(mSettings.getCameraId(),
64 | mSettings.getRecProfile()).videoFrameRate;
65 | }
66 |
67 | int duration = (int) (((double) (mSettings.getStopRecAfter() / mSettings.getCaptureRate())) / mRate * 1000); //Duration of the video
68 | if (duration < 500) {
69 | handleError(getClass().getSimpleName(),
70 | mContext.getString(R.string.pref_stop_recording_after)
71 | + " is too short in relation to the "
72 | + mContext.getString(R.string.pref_capture_rate));
73 | Toast.makeText(mContext, mContext.getString(R.string.error_too_short), Toast.LENGTH_SHORT).show();
74 | return;
75 | }
76 |
77 | mMediaRecorder.setMaxDuration(duration);
78 | mMediaRecorder.setOnInfoListener(this);
79 | }
80 |
81 | Log.i(getClass().getSimpleName(), "Starting video recording");
82 | mMediaRecorder.setOnErrorListener(this);
83 | if (mSettings.getVideoEncodingBitRate()>0) mMediaRecorder.setVideoEncodingBitRate(mSettings.getVideoEncodingBitRate());
84 | mMediaRecorder.prepare();
85 |
86 | mMediaRecorder.start();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/rest/HttpOutput.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec.rest;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * HTTP Output writer
7 | */
8 | public interface HttpOutput {
9 |
10 | /**
11 | * Send HTTP Header
12 | *
13 | * @param code Code
14 | * @param contentType Content Type
15 | * @throws IOException
16 | */
17 | public void sendReplyHeader(ReplyCode code, String contentType) throws IOException;
18 |
19 | /**
20 | * Send Line
21 | *
22 | * @param line Line
23 | */
24 | public void sendLine(String line) throws IOException;
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/rest/ListFolderHtml.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec.rest;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 |
8 | /**
9 | * List Folder user clickable
10 | */
11 | public class ListFolderHtml extends ListFolderPlain {
12 |
13 | /**
14 | * Current listing folder
15 | */
16 | private String listFolder;
17 |
18 | /**
19 | * Constructor
20 | *
21 | * @param out Output
22 | * @param rootDir Root directory
23 | */
24 | public ListFolderHtml(HttpOutput out, File rootDir) {
25 | super(out, rootDir);
26 | }
27 |
28 | @Override
29 | public boolean output(String listFolder) throws IOException {
30 | this.listFolder = listFolder;
31 | boolean result = super.output(listFolder);
32 | out.sendLine("");
41 | out.sendLine("List folder
");
42 | }
43 |
44 | @Override
45 | protected void writeFile(File file) throws IOException {
46 | // No escaping... The File should not contiain special chars, so keep it simple
47 | if (file.isDirectory()) {
48 | out.sendLine("" + file.getName() + "
");
49 | } else {
50 | out.sendLine("" + file.getName() + "
");
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/rest/ListFolderPlain.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec.rest;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 |
8 | /**
9 | * List Folder for REST API
10 | */
11 | public class ListFolderPlain {
12 |
13 | /**
14 | * Log Tag
15 | */
16 | protected static final String TAG = HttpThread.class.getSimpleName();
17 |
18 | /**
19 | * Output
20 | */
21 | protected final HttpOutput out;
22 |
23 | /**
24 | * Root directory
25 | */
26 | protected final File rootDir;
27 |
28 | /**
29 | * Constructor
30 | *
31 | * @param out Output
32 | * @param rootDir Root directory
33 | */
34 | public ListFolderPlain(HttpOutput out, File rootDir) {
35 | this.out = out;
36 | this.rootDir = rootDir;
37 | }
38 |
39 | public boolean output(String listFolder) throws IOException {
40 | File listDir = new File(rootDir, listFolder);
41 | Log.d(TAG, "List dir: " + listDir);
42 | if (!listDir.isDirectory()) {
43 | Log.w(TAG, "Directory does not exists");
44 | return false;
45 | }
46 |
47 | File[] files = listDir.listFiles();
48 | if (files == null) {
49 | Log.w(TAG, "Directory could not be listed");
50 | return false;
51 | }
52 |
53 | writeHeader();
54 |
55 | for (File f : files) {
56 | if (f.getName().charAt(0) == '.') {
57 | continue;
58 | }
59 |
60 | writeFile(f);
61 | }
62 |
63 | return true;
64 | }
65 |
66 | /**
67 | * Write out HTTP Header
68 | */
69 | protected void writeHeader() throws IOException {
70 | out.sendReplyHeader(ReplyCode.FOUND, "text/plain");
71 | }
72 |
73 | /**
74 | * Write out a file
75 | *
76 | * @param file File
77 | */
78 | protected void writeFile(File file) throws IOException {
79 | out.sendLine(file.getName());
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/rest/ReplyCode.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec.rest;
2 |
3 | /**
4 | * HTTP Reply codes
5 | */
6 | public enum ReplyCode {
7 |
8 | /**
9 | * Found
10 | */
11 | FOUND(200, "Found"),
12 |
13 | /**
14 | * Forbidden
15 | */
16 | FORBIDDEN(403, "Forbidden"),
17 |
18 | /**
19 | * File was not found
20 | */
21 | NOT_FOUND(404, "Not found");
22 |
23 | /**
24 | * HTTP Code
25 | */
26 | public final int code;
27 |
28 | /**
29 | * Result Text
30 | */
31 | public final String text;
32 |
33 | /**
34 | * Constructor
35 | *
36 | * @param code HTTP Code
37 | * @param text Result Text
38 | */
39 | private ReplyCode(int code, String text) {
40 | this.code = code;
41 | this.text = text;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/rest/TcpListener.java:
--------------------------------------------------------------------------------
1 | package at.andreasrohner.spartantimelapserec.rest;
2 |
3 | import java.net.ServerSocket;
4 | import java.net.Socket;
5 |
6 | import android.util.Log;
7 |
8 |
9 | /**
10 | * HTTP REST API Service, TCP Listener Service
11 | *
12 | * Inspirated by SwiFTP https://github.com/ppareit/swiftp/blob/master/app/src/main/java/be/ppareit/swiftp/server/TcpListener.java
13 | *
14 | * (c) Andreas Butti, 2024
15 | */
16 | public class TcpListener extends Thread {
17 |
18 | /**
19 | * Log Tag
20 | */
21 | private static final String TAG = TcpListener.class.getSimpleName();
22 |
23 | /**
24 | * Socket for incoming connections
25 | */
26 | private ServerSocket listenSocket;
27 |
28 | /**
29 | * HTTP REST API Service
30 | */
31 | private RestService restService;
32 |
33 | /**
34 | * Constructor
35 | *
36 | * @param listenSocket Socket for incoming connections
37 | * @param restService HTTP REST API Service
38 | */
39 | public TcpListener(ServerSocket listenSocket, RestService restService) {
40 | this.listenSocket = listenSocket;
41 | this.restService = restService;
42 | }
43 |
44 | /**
45 | * Close listening Socket
46 | */
47 | public void quit() {
48 | try {
49 | listenSocket.close(); // if the TcpListener thread is blocked on accept,
50 | // closing the socket will raise an exception
51 | } catch (Exception e) {
52 | Log.w(TAG, "Exception closing TcpListener listenSocket",e);
53 | }
54 | }
55 |
56 | @Override
57 | public void run() {
58 | try {
59 | while (true) {
60 | Socket clientSocket = listenSocket.accept();
61 | Log.i(TAG, "New connection from "+ clientSocket.getRemoteSocketAddress());
62 | HttpThread newSession = new HttpThread(clientSocket, restService);
63 | newSession.start();
64 | restService.registerSessionThread(newSession);
65 | }
66 | } catch (Exception e) {
67 | Log.d(TAG, "Exception in TcpListener", e);
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/sensor/CameraSettings.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.sensor;
20 |
21 | import java.util.ArrayList;
22 | import java.util.Collections;
23 | import java.util.Comparator;
24 | import java.util.List;
25 | import java.util.Set;
26 | import java.util.TreeSet;
27 | import android.content.SharedPreferences;
28 | import android.hardware.Camera;
29 | import android.hardware.Camera.Size;
30 | import android.media.CamcorderProfile;
31 |
32 | public class CameraSettings {
33 |
34 | private Camera.Parameters[] cameraParams;
35 |
36 | private synchronized Camera.Parameters getCameraParameters(int camId) {
37 | if (cameraParams == null)
38 | cameraParams = new Camera.Parameters[Camera.getNumberOfCameras()];
39 |
40 | Camera.Parameters params = cameraParams[camId];
41 | if (params == null) {
42 | Camera camera = Camera.open(camId);
43 | params = camera.getParameters();
44 | camera.release();
45 | cameraParams[camId] = params;
46 | }
47 |
48 | return params;
49 | }
50 |
51 | private Set getStringSet(SharedPreferences prefs, String key,
52 | Set defValues) {
53 | return prefs.getStringSet(key, defValues);
54 | }
55 |
56 | private void putStringSet(SharedPreferences prefs, String key,
57 | Set set) {
58 | prefs.edit().putStringSet(key, set).commit();
59 | return;
60 |
61 | }
62 |
63 | private void addProfileFrameRate(int camId, List list, int profile) {
64 | try {
65 | if (CamcorderProfile.hasProfile(camId, profile)) {
66 | CamcorderProfile p = CamcorderProfile.get(camId, profile);
67 | list.add(p.videoFrameRate);
68 | }
69 | } catch (Exception e) {
70 | }
71 | }
72 |
73 | private List getFrameRatesFromCameraProfile(int camId) {
74 | List list = new ArrayList();
75 |
76 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_HIGH);
77 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_LOW);
78 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_CIF);
79 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_1080P);
80 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_720P);
81 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_480P);
82 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_QCIF);
83 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_QVGA);
84 |
85 | // deduplicate sizes
86 | Set set = new TreeSet(list);
87 | list.clear();
88 | list.addAll(set);
89 |
90 | // sort
91 | Collections.sort(list);
92 |
93 | return list;
94 | }
95 |
96 | private List getFrameRatesFromCamera(int camId) {
97 | Camera.Parameters params = getCameraParameters(camId);
98 |
99 | List ranges = params.getSupportedPreviewFpsRange();
100 | List fpsList = new ArrayList();
101 | int prevFps = -1;
102 |
103 | if (ranges == null)
104 | return getFrameRatesFromCameraProfile(camId);
105 |
106 | for (int i = 0; i < ranges.size(); ++i) {
107 | int fps = ranges.get(i)[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000;
108 | if (prevFps == fps)
109 | continue;
110 | prevFps = fps;
111 |
112 | fpsList.add(fps);
113 | }
114 |
115 | return fpsList;
116 | }
117 |
118 | public void prefetch(final SharedPreferences prefs) {
119 | new Thread(new Runnable() {
120 | @Override
121 | public void run() {
122 | for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
123 | getFrameRates(prefs, i);
124 | }
125 | }
126 | }).start();
127 | }
128 |
129 | public List getFrameRates(SharedPreferences prefs, int camId) {
130 | List fpsIntList;
131 | Set fpsList = getStringSet(prefs, "pref_frame_rate_values_"
132 | + camId, null);
133 |
134 | if (fpsList == null) {
135 | fpsList = new TreeSet();
136 | fpsIntList = getFrameRatesFromCamera(camId);
137 |
138 | for (Integer fps : fpsIntList) {
139 | fpsList.add(fps.toString());
140 | }
141 |
142 | putStringSet(prefs, "pref_frame_rate_values_" + camId, fpsList);
143 | return fpsIntList;
144 | }
145 |
146 | fpsIntList = new ArrayList();
147 | for (String fps : fpsList) {
148 | try {
149 | fpsIntList.add(Integer.valueOf(fps));
150 | } catch (NumberFormatException e) {
151 | }
152 | }
153 |
154 | Collections.sort(fpsIntList);
155 |
156 | return fpsIntList;
157 | }
158 |
159 | private void addProfileFrameSize(int camId, List list, int profile) {
160 | try {
161 | if (CamcorderProfile.hasProfile(camId, profile)) {
162 | CamcorderProfile p = CamcorderProfile.get(camId, profile);
163 | list.add(new int[] { p.videoFrameWidth, p.videoFrameHeight });
164 | }
165 | } catch (Exception e) {
166 | }
167 | }
168 |
169 | private void prepareSizeList(List sizes) {
170 | Comparator comp = new Comparator() {
171 | @Override
172 | public int compare(int[] lhs, int[] rhs) {
173 | if (lhs[0] > rhs[0])
174 | return 1;
175 | if (lhs[0] < rhs[0])
176 | return -1;
177 | if (lhs[1] > rhs[1])
178 | return 1;
179 | if (lhs[1] < rhs[1])
180 | return -1;
181 | return 0;
182 | }
183 | };
184 |
185 | // deduplicate sizes
186 | Set set = new TreeSet(comp);
187 | set.addAll(sizes);
188 | sizes.clear();
189 | sizes.addAll(set);
190 |
191 | // sort
192 | Collections.sort(sizes, comp);
193 | }
194 |
195 | private List getFrameSizesFromProfiles(int camId, boolean timeLapse) {
196 | List list = new ArrayList();
197 |
198 | if (timeLapse) {
199 | addProfileFrameSize(camId, list,
200 | CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
201 | addProfileFrameSize(camId, list,
202 | CamcorderProfile.QUALITY_TIME_LAPSE_LOW);
203 | addProfileFrameSize(camId, list,
204 | CamcorderProfile.QUALITY_TIME_LAPSE_CIF);
205 | addProfileFrameSize(camId, list,
206 | CamcorderProfile.QUALITY_TIME_LAPSE_1080P);
207 | addProfileFrameSize(camId, list,
208 | CamcorderProfile.QUALITY_TIME_LAPSE_720P);
209 | addProfileFrameSize(camId, list,
210 | CamcorderProfile.QUALITY_TIME_LAPSE_480P);
211 | addProfileFrameSize(camId, list,
212 | CamcorderProfile.QUALITY_TIME_LAPSE_QCIF);
213 | addProfileFrameSize(camId, list,
214 | CamcorderProfile.QUALITY_TIME_LAPSE_QVGA);
215 | } else {
216 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_HIGH);
217 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_LOW);
218 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_CIF);
219 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_1080P);
220 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_720P);
221 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_480P);
222 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_QCIF);
223 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_QVGA);
224 | }
225 |
226 | prepareSizeList(list);
227 |
228 | return list;
229 | }
230 |
231 | public List getFrameSizes(SharedPreferences prefs, int camId,
232 | boolean timeLapse) {
233 |
234 | Set sizes = getStringSet(prefs, "pref_frame_size_values_"
235 | + camId, null);
236 | List sizesList = new ArrayList();
237 |
238 | if (sizes == null) {
239 | Camera.Parameters params = getCameraParameters(camId);
240 |
241 | List suppSizes = params.getSupportedVideoSizes();
242 | if (suppSizes == null)
243 | return getFrameSizesFromProfiles(camId, timeLapse);
244 |
245 | sizes = new TreeSet();
246 | for (Size s : suppSizes) {
247 | sizes.add(s.width + "x" + s.height);
248 | sizesList.add(new int[] { s.width, s.height });
249 | }
250 |
251 | putStringSet(prefs, "pref_frame_size_values_" + camId, sizes);
252 |
253 | prepareSizeList(sizesList);
254 |
255 | return sizesList;
256 | }
257 |
258 | for (String s : sizes) {
259 | String[] tmp = s.split("x");
260 | sizesList.add(new int[] { Integer.valueOf(tmp[0]),
261 | Integer.valueOf(tmp[1]) });
262 | }
263 |
264 | prepareSizeList(sizesList);
265 |
266 | return sizesList;
267 | }
268 |
269 | public List getPictureSizes(SharedPreferences prefs, int camId) {
270 | Set sizes = getStringSet(prefs, "pref_picture_size_values_"
271 | + camId, null);
272 | List sizesList = new ArrayList();
273 |
274 | if (sizes == null) {
275 | Camera.Parameters params = getCameraParameters(camId);
276 |
277 | sizes = new TreeSet();
278 | for (Size s : params.getSupportedPictureSizes()) {
279 | sizes.add(s.width + "x" + s.height);
280 | sizesList.add(new int[] { s.width, s.height });
281 | }
282 |
283 | putStringSet(prefs, "pref_picture_size_values_" + camId, sizes);
284 |
285 | prepareSizeList(sizesList);
286 |
287 | return sizesList;
288 | }
289 |
290 | for (String s : sizes) {
291 | String[] tmp = s.split("x");
292 | sizesList.add(new int[] { Integer.valueOf(tmp[0]),
293 | Integer.valueOf(tmp[1]) });
294 | }
295 |
296 | prepareSizeList(sizesList);
297 |
298 | return sizesList;
299 | }
300 |
301 | public int getMinExposureCompensation(int camId){
302 | Camera.Parameters params = getCameraParameters(camId);
303 | return params.getMinExposureCompensation();
304 | }
305 |
306 | public int getMaxExposureCompensation(int camId){
307 | Camera.Parameters params = getCameraParameters(camId);
308 | return params.getMaxExposureCompensation();
309 | }
310 |
311 | public int getMaxZoom(int camId){
312 | Camera.Parameters params = getCameraParameters(camId);
313 | if (params.isZoomSupported())
314 | return params.getMaxZoom();
315 | else
316 | return 0;
317 | }
318 |
319 | }
320 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/sensor/MuteShutter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.sensor;
20 |
21 | import android.app.NotificationManager;
22 | import android.content.Context;
23 | import android.media.AudioManager;
24 | import android.os.Handler;
25 |
26 | public class MuteShutter {
27 | private int mode;
28 | private int ringerMode;
29 | private static final int[] streams = new int[] { AudioManager.STREAM_DTMF,
30 | AudioManager.STREAM_MUSIC, AudioManager.STREAM_NOTIFICATION,
31 | AudioManager.STREAM_SYSTEM };
32 | private int[] streamVolumes = new int[streams.length];
33 | private AudioManager audioManager;
34 | private NotificationManager notificationManager;
35 | private final Handler handler = new Handler();
36 | private volatile boolean muted;
37 |
38 | public MuteShutter(Context context) {
39 | this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
40 | this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
41 | muted = false;
42 | }
43 |
44 | private void storeSoundSettings() {
45 | mode = audioManager.getMode();
46 | ringerMode = audioManager.getRingerMode();
47 |
48 | for (int i = 0; i < streams.length; ++i)
49 | streamVolumes[i] = audioManager.getStreamVolume(streams[i]);
50 | }
51 |
52 | private void recoverSoundSettings() {
53 | audioManager.setMode(mode);
54 | audioManager.setRingerMode(ringerMode);
55 |
56 | for (int i = 0; i < streams.length; ++i)
57 | audioManager.setStreamVolume(streams[i], streamVolumes[i],
58 | AudioManager.FLAG_ALLOW_RINGER_MODES);
59 | }
60 |
61 | public void maxAllStreams() {
62 | for (int stream : streams) {
63 | audioManager.setStreamMute(stream, false);
64 | audioManager.setStreamVolume(stream, audioManager.getStreamMaxVolume(stream),
65 | AudioManager.FLAG_ALLOW_RINGER_MODES);
66 | }
67 | }
68 |
69 | public synchronized void unmuteShutter() {
70 | if (!muted)
71 | return;
72 |
73 | maxAllStreams();
74 |
75 | recoverSoundSettings();
76 | muted = false;
77 | }
78 |
79 | private void restartHandler() {
80 | // remove all Runnables
81 | handler.removeCallbacksAndMessages(null);
82 | handler.postDelayed(new Runnable() {
83 | public void run() {
84 | unmuteShutter();
85 | }
86 | }, 2000);
87 | }
88 |
89 | public synchronized void muteShutter() {
90 | if (muted) {
91 | restartHandler();
92 | return;
93 | }
94 |
95 | storeSoundSettings();
96 |
97 |
98 | if (notificationManager.isNotificationPolicyAccessGranted()) {
99 | for (int stream : streams) {
100 | audioManager.setStreamVolume(stream, 0,
101 | AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
102 | audioManager.setStreamMute(stream, true);
103 | }
104 | audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
105 | }
106 |
107 | muted = true;
108 |
109 | restartHandler();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/java/at/andreasrohner/spartantimelapserec/sensor/OrientationSensor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app
3 | * Copyright (C) 2014 Andreas Rohner
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package at.andreasrohner.spartantimelapserec.sensor;
20 |
21 | import android.content.Context;
22 | import android.hardware.Camera;
23 | import android.hardware.Camera.CameraInfo;
24 | import android.view.OrientationEventListener;
25 |
26 | public class OrientationSensor extends OrientationEventListener {
27 | private int mOrientation;
28 |
29 | public OrientationSensor(Context context) {
30 | super(context);
31 |
32 | }
33 |
34 | @Override
35 | public void onOrientationChanged(int orientation) {
36 | if (orientation != ORIENTATION_UNKNOWN)
37 | mOrientation = orientation;
38 | }
39 |
40 | public int getCameraRotation(int cameraId) {
41 | Camera.CameraInfo info = new Camera.CameraInfo();
42 | Camera.getCameraInfo(cameraId, info);
43 |
44 | mOrientation = (mOrientation + 45) / 90 * 90;
45 | int rotation = 0;
46 | if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
47 | rotation = (info.orientation - mOrientation + 360) % 360;
48 | } else { // back-facing camera
49 | rotation = (info.orientation + mOrientation) % 360;
50 | }
51 |
52 | return rotation;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_camera.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
20 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
17 |
24 |
31 |
38 |
45 |
52 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_monochrome.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_radio_button_checked_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_radio_button_checked_disabled_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_stop_circle_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_stop_circle_disabled_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_visibility_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_visibility_disabled_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_date_preference.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
10 |
11 |
15 |
16 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_date_widget_preference.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_intervalpicker_preference.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
14 |
15 |
21 |
22 |
28 |
33 |
38 |
39 |
45 |
50 |
55 |
56 |
62 |
67 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_seekbar_preference.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
19 |
20 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/raw/favicon.ico
--------------------------------------------------------------------------------
/app/src/main/res/raw/help.txt:
--------------------------------------------------------------------------------
1 | HELP
2 |
3 | GET /: Show this Help
4 |
5 | REST API v1:
6 | GET /1/ctrl/status: Get current state: [stopped/running]
7 | GET /1/ctrl/start: Start recording
8 | GET /1/ctrl/stop: Stop recording
9 | GET /1/ctrl/param: Get parameter
10 | GET /1/device/battery: Get battery percentage
11 | GET /1/current/img: Current / last recorded image
12 | GET /1/current/imgcount: Image count
13 | GET /1/current/lastimg: Last image: Name, Timestamp and URL
14 | GET /1/img/list: List image folders
15 | GET /1/img/listhtml: user clickable HTML page
16 | GET /1/img//list: List folder / images
17 | GET /1/img///list: List folder / images
18 | GET /1/img//.../: Download image
19 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
108 |
109 |
384 | TimeLapseCam
385 |
386 |
387 |
388 | TimeLapseCam
389 |
395 |
396 |
397 |
398 |
399 |
400 | Last refresh |
401 | --- |
402 |
403 |
404 | State |
405 | --- |
406 |
407 |
408 | Battery |
409 | --- |
410 |
411 |
412 | Mode |
413 | --- |
414 |
415 |
416 | Last image |
417 | --- |
418 |
419 |
420 | Image count |
421 | --- |
422 |
423 |
424 | Next image |
425 | --- |
426 |
427 |
428 | Interval |
429 | --- |
430 |
431 |
432 |
433 |
434 |
Refresh to get current image / No image to display
435 |
436 |
437 |
440 |
441 |
442 |
--------------------------------------------------------------------------------
/app/src/main/res/values-de/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Normales Video (MP4)
5 | - Zeitraffer Video (MP4)
6 | - Zeitraffer Bilder (JPG)
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | TimeLapseCam
4 | Start
5 | Stop
6 | Lautstärke zurücksetzen
7 | Galerie öffnen
8 | Galerie öffnen
9 | Vorschau öffnen
10 | Aufnahme starten
11 | Aufnahme stoppen
12 | Lautstärke zurücksetzen
13 | Einstellungen
14 | Galerie öffnen
15 | Vorschau öffnen
16 | Aufnahme läuft
17 | Verhindere Kameraauslösegeräusch & Klingelton
18 | Versucht, das Telefon auf stumm zu schalten um das Auslösegeräusch der Kamera zu unterdrücken. Versucht außerdem, den Klingelton des Telefons abzustellen. Benötigt separate Berechtigung!
19 | Kamera auswählen
20 | Kamera
21 | vorne
22 | hinten
23 | Aufnahmemodus
24 | Projekttitel
25 | Der Projekttitel wird zur Erzeugung eines Verzeichnisses für die Aufnahmen verwendet
26 | Video Abspielrate
27 | Auflösung
28 | Kamera Abtastintervall
29 | Das Intervall in dem Bilder aufgenommen werden
30 | Anfangsverzögerung
31 | Auswahl einer Verzögerung bevor die Aufnahme beginnt
32 | JPEG Qualität
33 | Auswahl der JPEG Qualität (die Einstellung hat auf den meisten Geräten keinen Effekt)
34 | Wenig Akku
35 | Aufnahme stoppen wenn der Ladestand des Akkus gering ist
36 | Wenig Speicherplatz
37 | Aufnahme stoppen wenn der Speicherplatz zur Neige geht
38 | Aufnahme Planen
39 | Aufnahme stoppen nach
40 | Unendlich
41 | Bild
42 | Projekt Einstellungen
43 | Kamera Einstellungen
44 | Planung Einstellungen
45 | Benachrichtigungs Icon
46 | Auswahl des Icons das während der Aufnahme erscheint
47 | fps
48 | ms
49 | s
50 | s
51 | min
52 | min
53 | std
54 | std
55 | Belichtungskorrektur
56 | Aufnahme wird vorbereitet
57 | Berechtigung fehlt
58 | Läuft bereits
59 | Zoom
60 |
61 |
62 | OK
63 | Nein
64 | Vielleicht später
65 | Mögen Sie diese App? Bitte vergeben Sie einen Stern auf GitHub oder spendieren Sie dem Entwickler einen Kaffee über PayPal.
66 | Hardware spezifische Einstellungen
67 | Verzögerung für Kamerastart. Wenn zu niedrig, kann es zum Absturz kommen!
68 | Verzögerung, bevor die Kamera ein Bild aufnimmt. Wenn zu niedrig, kann das Bild dunkel sein!
69 | Verzögerung für Kamerainitialisierung
70 | Verzögerung für Kameraauslöser
71 | Über
72 | Blitz verwenden, falls verfügbar
73 | Für maximale Rate auf 0 setzen (Voreinstellung). Die tatsächliche Bitrate kann durch die Mindestqualität des Codecs beeinflusst werden.
74 | %s bit/s
75 | Beste Qualität
76 | Wert in bit/s eingeben
77 | Bitrate der Videocodierung
78 | Video zu kurz
79 | REST API
80 | REST API aktiviert
81 | HTTP REST API Server aktivieren für Fernsteuerung
82 | HTTP Server Port
83 | Passwort
84 | Port
85 | Port, 1024 - 65535, Standard 8085
86 | Passwort für den REST API Zugriff
87 | Passwort
88 | REST API gestartet...
89 | REST API gestoppt...
90 | Port ungültig, 8085 wird verwendet
91 | Verbindungsinformationen
92 | Keine IP! Berühren zum neu laden.
93 | REST API Läuft
94 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | TimeLapseCam
4 | Başlat
5 | Durdur
6 | Ses Seviyesini Sıfırla
7 | Galeriyi Aç
8 | Galeriyi Aç
9 | Ön İzlemeyi Aç
10 | Kaydı Başlat
11 | Kaydı Durdur
12 | Ses Seviyesini Sıfırla
13 | Ayarlar
14 | Galeriyi Aç
15 | Ön İzlemeyi Aç
16 | Kayıt devam ediyor
17 | Deklanşör ve Zil Sesini Kapat
18 | Fotoğraf çekerken deklanşör sesini çalmasını önlemek için telefonu sessize almaya çalışır. Ayrıca telefonun zil sesini de kapatmaya çalışır. İzin gerekir!
19 | Kamera Seç
20 | Kamera
21 | ön
22 | arka
23 | Kayıt Modu
24 | Proje Adı
25 | Proje adı, oluşturulan dosyalar için dizin adı ve ön ek olarak kullanılır
26 | Oynatım Kare Hızı
27 | Kare Boyutu
28 | Kamera Çekim Aralığı
29 | Çekim aralığını seçin
30 | Başlangıç Gecikmesi
31 | Çekim başlamadan önceki gecikmeyi seçin
32 | JPEG Kalitesi
33 | JPEG kalitesini seçin (bu ayarın bazı aygıtlarda etkisi yoktur)
34 | Düşük Pil
35 | Pil seviyesi azaldığında kaydı durdurun
36 | Düşük Depolama Alanı
37 | Depolama alanı azalırsa kaydı durdurun
38 | Kaydı Zamanla
39 | Şundan Sonra Kaydı Durdur
40 | Sonsuz
41 | Simge
42 | Proje ayarları
43 | Kamera ayarları
44 | Zamanlama ayarları
45 | Bildirim Simgesi
46 | Kayıt sırasında gösterilen bildirim simgesini seçin
47 | fps
48 | ms
49 | sn
50 | sn
51 | dak
52 | dak
53 | sa
54 | sa
55 | Poz Dengelemesi
56 | Kayıt hazırlanıyor
57 | Eksik izin
58 | Zaten çalışıyor
59 | Yakınlaştır
60 |
61 |
62 | Tamam
63 | Hayır
64 | Belki daha sonra
65 | Bu uygulamayı beğendiniz mi? Lütfen GitHub\'da yıldız verin veya geliştiriciye PayPal aracılığıyla bir kahve ısmarlayın.
66 | Donanıma özel ayarlar
67 | Kamera başlangıcı için gecikme. Çok küçükse çökme meydana gelebilir!
68 | Kamera fotoğraf çekmeden önceki gecikme. Çok düşükse, görüntü karanlık olabilir!
69 | Kamera başlangıç gecikmesi
70 | Kamera çekim gecikmesi
71 | Hakkında
72 | Varsa flaş kullan
73 | En yüksek hız için 0 olarak ayarlayın (öntanımlı). Gerçek bit hızı, kod çözücünün gerektirdiği en düşük kaliteden etkilenebilir.
74 | %s bit/sn
75 | En iyi kalite
76 | bit/sn cinsinden değer girin
77 | Video Kodlama Bit Hızı
78 | Video çok kısa
79 | REST API
80 | REST API etkinleştirildi
81 | Uzaktan denetim için HTTP REST API sunucusunu etkinleştirin
82 | HTTP sunucusu bağlantı noktası
83 | Parola
84 | Bağlantı noktası
85 | Bağlantı noktası, 1024 - 65535, öntanımlı 8085
86 | REST API erişimi için parola
87 | Parola
88 | REST API başlatıldı...
89 | REST API durduruldu...
90 | Bağlantı noktası geçersiz, 8085 kullan
91 | Bağlantı Bilgileri
92 | IP yok! Yenilemek için dokunun.
93 | REST API Çalışıyor
94 |
95 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Normal Video (MP4)
5 | - Time Lapsed Video (MP4)
6 | - Time Lapsed Images (JPG)
7 |
8 |
9 | - VIDEO
10 | - VIDEO_TIME_LAPSE
11 | - IMAGE_TIME_LAPSE
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14sp
5 | 14sp
6 | 20sp
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | TimeLapseCam
4 | Start
5 | Stop
6 | Reset Audio Volume
7 | Open Gallery
8 | Open Gallery
9 | Open Preview
10 | Start Recording
11 | Stop Recording
12 | Reset Audio Volume
13 | Settings
14 | Open Gallery
15 | Open Preview
16 | Recording is running
17 | Mute Shutter Sound & Ringtone
18 | Tries to mute the phone to prevent it from playing the shutter sound while taking a picture. Also tries to mute the ringtone of the phone. Need permission!
19 | Select Camera
20 | Camera
21 | front
22 | back
23 | Recording Mode
24 | Project Title
25 | The project title is used as a directory and prefix for the created files
26 | Playback Frame Rate
27 | Frame Size
28 | Camera Capture Interval
29 | Select the capture interval
30 | Initial Delay
31 | Select the delay before capturing starts
32 | JPEG Quality
33 | Select the JPEG quality (this setting has no effect on some devices)
34 | Low Battery
35 | Stop recording if the battery level gets low
36 | Low Storage Space
37 | Stop recording if the storage space gets low
38 | Schedule Recording
39 | Stop Recording After
40 | Infinite
41 | Icon
42 | Project settings
43 | Camera settings
44 | Scheduling settings
45 | Notification Icon
46 | Select the notification icon shown while recording
47 | fps
48 | ms
49 | sec
50 | secs
51 | min
52 | mins
53 | hour
54 | hours
55 | Exposure Compensation
56 | Preparing recording
57 | Missing permission
58 | Already running
59 | Zoom
60 |
61 |
62 | OK
63 | No
64 | Maybe later
65 | Do you like this app? Please give a star on GitHub or buy the developer a coffee via PayPal.
66 | Hardware specific settings
67 | Delay for camera startup. If too small crash can occur!
68 | Delay before camera takes picture. If too low, image may be dark!
69 | Camera init delay
70 | Camera trigger delay
71 | About
72 | Use Flash if available
73 | Set 0 for maximum rate (default). The actual bitrate may be affected by the minimum quality required by the codec.
74 | %s bit/s
75 | Best quality
76 | Enter value in bit/s
77 | Video Encoding Bit Rate
78 | Video too short
79 | REST API
80 | REST API enabled
81 | Enable HTTP REST API server for remote control
82 | HTTP server port
83 | Password
84 | Port
85 | Port, 1024 - 65535, default 8085
86 | Password to access the REST API
87 | Password
88 | REST API started...
89 | REST API stopped...
90 | Port invalid, use 8085
91 | Connection Info
92 | No IP! Tap to refresh.
93 | REST API Running
94 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
21 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
14 |
15 |
21 |
22 |
23 |
24 |
29 |
30 |
34 |
35 |
39 |
40 |
44 |
48 |
49 |
55 |
63 |
64 |
71 |
72 |
76 |
77 |
82 |
83 |
84 |
85 |
93 |
101 |
102 |
103 |
104 |
105 |
114 |
122 |
123 |
127 |
128 |
133 |
138 |
139 |
140 |
141 |
145 |
146 |
151 |
152 |
159 |
160 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '7.1.1' apply false
4 | id 'com.android.library' version '7.1.1' apply false
5 | }
6 |
7 | task clean(type: Delete) {
8 | delete rootProject.buildDir
9 | }
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/10.txt:
--------------------------------------------------------------------------------
1 | Erste Version
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/11.txt:
--------------------------------------------------------------------------------
1 | Verbesserte Einstellung für das Aufnahmeintervall
2 | Option für Blitz im JPG Modus
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/12.txt:
--------------------------------------------------------------------------------
1 | Fehlerbehebung für geplante Aufnahme
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/13.txt:
--------------------------------------------------------------------------------
1 | Neue Option: Bitrate der Videocodierung einstellen
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/14.txt:
--------------------------------------------------------------------------------
1 | Start/Stop/Vorschau in ActionBar verschoben
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/15.txt:
--------------------------------------------------------------------------------
1 | Update auf Android 13 SDK
2 | Unterstützung für monochromes Icon
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/16.txt:
--------------------------------------------------------------------------------
1 | Update auf Android 13 SDK
2 | Aufnahme stoppen, wenn Gerät ausgeschaltet wird
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/17.txt:
--------------------------------------------------------------------------------
1 | REST API
2 | Fernbedienung über http Schnittstelle
3 | (Danke an andreasb242)
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/18.txt:
--------------------------------------------------------------------------------
1 | Fehlerbehebung
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/full_description.txt:
--------------------------------------------------------------------------------
1 | Minimalistische App, die Zeitraffervideos im Hintergrund bei ausgeschaltetem Bildschirm aufzeichnet.
2 | Zeitraffervideos können in verschiedenen Auflösungen gespeichert werden als MP4-Videodatei oder als einzelne JPEG-Dateien.
3 | Während der Aufnahme ist keine Vorschau sichtbar und der Bildschirm kann ausgeschaltet werden.
4 | Es gibt kein Aufnahmelimit, aber die App stoppt die Aufnahme, wenn der Akku Ladezustand oder der Speicherplatz niedrig ist.
5 |
6 | Funktionen:
7 |
8 | * Normales Video aufnehmen (MP4)
9 | * Zeitraffervideo aufnehmen (MP4/JPEG)
10 | * Wählen Sie verschiedene Auflösungen
11 | * Wählen Sie die zu verwendende Kamera aus
12 | * Planen Sie die Aufnahme an einem bestimmten Datum
13 | * Vorschau mit ausgewählter Auflösung
14 | * Verschlussgeräusch deaktivieren
15 |
16 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/short_description.txt:
--------------------------------------------------------------------------------
1 | Minimalistische App, die Zeitraffervideos aufzeichnet
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/title.txt:
--------------------------------------------------------------------------------
1 | TimeLapseCam
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/10.txt:
--------------------------------------------------------------------------------
1 | Initial version
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/11.txt:
--------------------------------------------------------------------------------
1 | Improved setting for capture interval
2 | Option to use flash in JPG mode
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/12.txt:
--------------------------------------------------------------------------------
1 | Bugfix for scheduled recording
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/13.txt:
--------------------------------------------------------------------------------
1 | Add option to set video encoding bitrate
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/14.txt:
--------------------------------------------------------------------------------
1 | Move start/stop/preview to action bar
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/15.txt:
--------------------------------------------------------------------------------
1 | Update for Android 13 SDK
2 | Monochrome icon support
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/16.txt:
--------------------------------------------------------------------------------
1 | Update for Android 14 SDK
2 | Stop recording when device is shutdown
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/17.txt:
--------------------------------------------------------------------------------
1 | REST API
2 | Remote control via http interface
3 | (Thanks to andreasb242)
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/18.txt:
--------------------------------------------------------------------------------
1 | Bugfix
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/19.txt:
--------------------------------------------------------------------------------
1 | Ignore battery optimizations
2 | Turkish translation
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Minimalistic app that records time lapse videos in the background with the screen turned off.
2 | Time lapse videos can be saved in various resolutions as a MP4 video file, or as individual JPEG files.
3 | While recording there is no preview visible and the screen can be turned off.
4 | There is no recording limit, but the app will stop recording if battery level or disk space is low.
5 |
6 | Features:
7 |
8 | * Record normal video (MP4)
9 | * Record time lapse video (MP4/JPEG)
10 | * Select various resolutions
11 | * Select camera to use
12 | * Schedule recording on specific date
13 | * Preview with selected resolution
14 | * Disable shutter sound
15 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Minimalistic app that records time lapse videos
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | TimeLapseCam
--------------------------------------------------------------------------------
/fastlane/metadata/android/tr-TR/full_description.txt:
--------------------------------------------------------------------------------
1 | Ekran kapalıyken arka planda zaman aralıklı videolar kaydeden sade bir uygulama.
2 | Zaman aralıklı videolar çeşitli çözünürlüklerde MP4 video dosyası olarak veya ayrı JPEG dosyaları olarak kaydedilebilir.
3 | Kayıt sırasında ön izleme görünmez ve ekran kapatılabilir.
4 | Kayıt sınırı yoktur, ancak pil seviyesi veya disk alanı düşükse uygulama kaydı durduracaktır.
5 |
6 | Özellikler:
7 |
8 | * Normal video kaydedin (MP4)
9 | * Zaman aralıklı video kaydedin (MP4/JPEG)
10 | * Çeşitli çözünürlükleri seçin
11 | * Kullanılacak kamerayı seçin
12 | * Belirli bir tarihte kayıt planlayın
13 | * Seçilen çözünürlükle ön izleme yapın
14 | * Deklanşör sesini devre dışı bırakın
15 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/tr-TR/short_description.txt:
--------------------------------------------------------------------------------
1 | Zaman aralıklı videolar kaydeden sade uygulama
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/tr-TR/title.txt:
--------------------------------------------------------------------------------
1 | TimeLapseCam
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 15 12:47:39 CET 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
4 | distributionSha256Sum=f581709a9c35e9cb92e16f585d2c4bc99b2b1a5f85d2badbd3dc6bff59e1e6dd
5 | distributionPath=wrapper/dists
6 | zipStorePath=wrapper/dists
7 | zipStoreBase=GRADLE_USER_HOME
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "TimeLapse"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
");
33 |
34 | return result;
35 | }
36 |
37 | @Override
38 | protected void writeHeader() throws IOException {
39 | out.sendReplyHeader(ReplyCode.FOUND, "text/html");
40 | out.sendLine("